## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
This commit was merged in pull request #7.
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""EPIC R08-T13: TokenUsageCardWidget / UsageChartWidget / HabitsWidget /
|
||||
DashboardTab shell, real Qt offscreen.
|
||||
|
||||
``DashboardQueryService``'s own logic is unit tested (R08-T13, no Qt) in
|
||||
``tests/unit/test_dashboard_query_service.py``; this file proves the three
|
||||
widgets and the shell are actually wired to it and to each other (the period
|
||||
selector living on ``UsageChartWidget`` driving all three).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
from cowork_local.config import AppConfig # noqa: E402
|
||||
from cowork_local.core import usage_tracker as ut # noqa: E402
|
||||
from cowork_local.state import AppContext # noqa: E402
|
||||
|
||||
pytest.importorskip("PySide6", reason="Qt is required for the integration suite")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def qt_app():
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
return QApplication.instance() or QApplication([])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def usage_dir(tmp_path, monkeypatch):
|
||||
d = tmp_path / "usage"
|
||||
monkeypatch.setattr(ut, "USAGE_DIR", d)
|
||||
return d
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ctx(qt_app, tmp_path):
|
||||
return AppContext(AppConfig.load(tmp_path / "config.json"))
|
||||
|
||||
|
||||
def _write_event(usage_dir, day: date, **overrides):
|
||||
usage_dir.mkdir(parents=True, exist_ok=True)
|
||||
event = {
|
||||
"ts": f"{day.isoformat()}T10:00:00", "source": "cowork", "label": "Test chat",
|
||||
"provider": "anthropic", "model": "claude-sonnet-4-6",
|
||||
"in": 100, "out": 50, "cache": 0, "estimated": False,
|
||||
"account": "", "machine": "",
|
||||
}
|
||||
event.update(overrides)
|
||||
path = usage_dir / f"{day.isoformat()}.jsonl"
|
||||
with path.open("a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(event) + "\n")
|
||||
|
||||
|
||||
def test_dashboard_tab_builds_and_populates_cards(usage_dir, ctx):
|
||||
from cowork_local.presentation.dashboard.dashboard_tab import DashboardTab
|
||||
|
||||
_write_event(usage_dir, date.today())
|
||||
tab = DashboardTab(ctx) # refresh() runs once at construction
|
||||
|
||||
assert "100" in tab.token_cards.card_in.value_lbl.text() \
|
||||
or tab.token_cards.card_in.value_lbl.text() # non-crashing, has SOME text
|
||||
assert tab.token_cards.card_total.value_lbl.text() != ""
|
||||
|
||||
|
||||
def test_period_navigation_on_chart_refreshes_the_whole_shell(usage_dir, ctx):
|
||||
from cowork_local.presentation.dashboard.dashboard_tab import DashboardTab
|
||||
|
||||
_write_event(usage_dir, date.today())
|
||||
tab = DashboardTab(ctx)
|
||||
calls = []
|
||||
tab.refresh = lambda *a, orig=tab.refresh: (calls.append(1), orig(*a))[-1]
|
||||
|
||||
tab.chart._chart_prev()
|
||||
|
||||
assert calls == [1]
|
||||
|
||||
|
||||
def test_granularity_change_resets_offset_to_current(usage_dir, ctx):
|
||||
from cowork_local.presentation.dashboard.dashboard_tab import DashboardTab
|
||||
from cowork_local.presentation.dashboard.usage_chart_widget import UsageChartWidget
|
||||
|
||||
tab = DashboardTab(ctx)
|
||||
tab.chart._chart_offset = -3
|
||||
|
||||
idx = tab.chart.gran_combo.findData("month")
|
||||
tab.chart.gran_combo.setCurrentIndex(idx)
|
||||
|
||||
assert tab.chart.chart_offset == 0
|
||||
|
||||
|
||||
def test_currency_change_persists_and_triggers_refresh(usage_dir, ctx):
|
||||
from cowork_local.presentation.dashboard.dashboard_tab import DashboardTab
|
||||
|
||||
tab = DashboardTab(ctx)
|
||||
idx = tab.chart.currency_combo.findData("EUR") \
|
||||
if tab.chart.currency_combo.findData("EUR") >= 0 else 0
|
||||
seen = []
|
||||
tab.chart.currency_changed.connect(lambda: seen.append(1))
|
||||
|
||||
tab.chart.currency_combo.setCurrentIndex(idx)
|
||||
|
||||
if tab.chart.currency_combo.currentData() != "USD":
|
||||
assert seen == [1]
|
||||
assert ctx.config.data["usage"]["currency"] == tab.chart.currency_combo.currentData()
|
||||
|
||||
|
||||
def test_habits_widget_ai_analyze_noop_without_data(usage_dir, ctx):
|
||||
"""No events in range -> emits a status message instead of starting a
|
||||
background worker (matches the original _ai_analyze guard)."""
|
||||
from cowork_local.presentation.dashboard.habits_widget import HabitsWidget
|
||||
from cowork_local.application.monitoring import DashboardQueryService
|
||||
|
||||
query = DashboardQueryService(ctx)
|
||||
widget = HabitsWidget(ctx, query)
|
||||
widget.refresh(date(2020, 1, 1), date(2020, 1, 1))
|
||||
messages = []
|
||||
widget.status_message.connect(messages.append)
|
||||
|
||||
widget._ai_analyze()
|
||||
|
||||
assert messages # "no data" status, no worker started
|
||||
assert widget._ai_worker is None
|
||||
|
||||
|
||||
def test_budget_apply_updates_budget_card(usage_dir, ctx):
|
||||
from cowork_local.application.monitoring import DashboardQueryService
|
||||
from cowork_local.presentation.dashboard.token_usage_card_widget import TokenUsageCardWidget
|
||||
|
||||
query = DashboardQueryService(ctx)
|
||||
widget = TokenUsageCardWidget(ctx, query)
|
||||
widget.budget_card.budget_spin.setValue(50.0)
|
||||
|
||||
widget._apply_budget()
|
||||
|
||||
status = query.budget_status()
|
||||
assert status is not None
|
||||
assert status["amount_usd"] == pytest.approx(50.0)
|
||||
Reference in New Issue
Block a user