Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ jobs:

strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
# Python 3.9 has no setup-python build for Ubuntu 26.04 (latest).
os: [ubuntu-24.04, windows-latest, macOS-latest]
python_version: [3.9]

steps:
Expand Down
2 changes: 2 additions & 0 deletions aw_client/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def query(

@main.command(help="Generate an activity report")
@click.argument("hostname")
@click.option("--name", help="Query name (required with --cache)")
@click.option("--cache", is_flag=True)
@click.option("--start", default=now - td1day, type=click.DateTime())
@click.option("--stop", default=now + td1yr, type=click.DateTime())
Expand Down Expand Up @@ -250,6 +251,7 @@ def print_top(events: List[Event], key=lambda e: e.data, title="Events", n=10):

@main.command(help="Query 'canonical events' for a single host (filtered, classified)")
@click.argument("hostname")
@click.option("--name", help="Query name (required with --cache)")
@click.option("--cache", is_flag=True)
@click.option("--start", default=now - td1day, type=click.DateTime())
@click.option("--stop", default=now + td1yr, type=click.DateTime())
Expand Down
94 changes: 94 additions & 0 deletions tests/test_cli_cache_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Exercise real Click parsing and SDK query encoding without server traffic."""

import json
from datetime import datetime
from typing import Any, List

import pytest
import requests
from click.testing import CliRunner

from aw_client import ActivityWatchClient, cli


@pytest.fixture(params=["report", "canonical"])
def command(request):
return request.param


@pytest.fixture
def transport(monkeypatch, command):
calls = []
event = {
"timestamp": "2026-10-28T12:00:00+00:00",
"duration": 60,
"data": {"app": "editor", "title": "fixture", "$category": ["Work"]},
}
payload: List[Any] = (
[{"window": {"cat_events": [event], "title_events": [event]}}]
if command == "report"
else [[event]]
)

class FixtureClient:
# Bind the real query method without constructing config, locks or queues.
query = ActivityWatchClient.query

def __init__(self, *args, **kwargs):
pass

def _post(self, endpoint, data, params=None):
calls.append((endpoint, data, params))
response = requests.Response()
response.status_code = 200
response._content = json.dumps(payload).encode()
return response

monkeypatch.setattr(cli.aw_client, "ActivityWatchClient", FixtureClient)
monkeypatch.setattr(cli, "get_classes", lambda: cli.default_classes)
return calls


def invoke(command, *options):
return CliRunner().invoke(
cli.main,
[command, "fixture-host", "--start", "2026-10-28", "--stop", "2026-10-29"]
+ list(options),
)


def test_help_describes_cache_name(command, transport):
result = CliRunner().invoke(cli.main, [command, "--help"])
assert result.exit_code == 0, result.output
assert "--name TEXT" in result.output
assert "required with --cache" in result.output
assert transport == []


def test_named_cache_reaches_sdk_transport(command, transport):
uncached = invoke(command)
assert uncached.exit_code == 0, uncached.exception
assert "fixture" in uncached.output
assert "name" not in transport[0][2]
uncached_body = transport[0][1]
transport.clear()

result = invoke(command, "--cache", "--name", "fixture-query")
assert result.exit_code == 0, result.exception
assert result.output == uncached.output
assert len(transport) == 1
endpoint, body, params = transport[0]
assert endpoint == "query/"
assert params == {"name": "fixture-query", "cache": 1}
assert body == uncached_body
assert "fixture-host" in "\n".join(body["query"])
start, stop = body["timeperiods"][0].split("/")
assert datetime.fromisoformat(start).utcoffset() is not None
assert datetime.fromisoformat(stop).utcoffset() is not None


def test_unnamed_cache_keeps_sdk_guard(command, transport):
result = invoke(command, "--cache")
assert result.exit_code != 0
assert "not allowed to do caching without a query name" in str(result.exception)
assert transport == []
Loading