feat(skills): add datastore skill (DuckDB engine, JSONL partitioning, Garmin mirror, and view renderers)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import json
|
||||
import pytest
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from datastore import DatastoreEngine
|
||||
from garmin_sync import GarminSyncClient, GarminMirrorWriter, sync_date_range, generate_date_list
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_data_dir():
|
||||
temp_dir = tempfile.mkdtemp(prefix="garmin_test_")
|
||||
data_dir = Path(temp_dir)
|
||||
# create standard dirs
|
||||
(data_dir / "garmin").mkdir(parents=True)
|
||||
yield data_dir
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
def test_garmin_sync_mock_and_idempotency(temp_data_dir):
|
||||
client = GarminSyncClient(token_dir=temp_data_dir, mock=True)
|
||||
writer = GarminMirrorWriter(temp_data_dir)
|
||||
|
||||
dates = ["2026-10-01", "2026-10-02"]
|
||||
sync_date_range(client, writer, dates)
|
||||
|
||||
daily_file = temp_data_dir / "garmin" / "daily" / "2026-10.jsonl"
|
||||
sleep_file = temp_data_dir / "garmin" / "sleep" / "2026-10.jsonl"
|
||||
activities_file = temp_data_dir / "garmin" / "activities" / "2026-10.jsonl"
|
||||
|
||||
assert daily_file.is_file()
|
||||
assert sleep_file.is_file()
|
||||
assert activities_file.is_file()
|
||||
|
||||
# Read daily lines
|
||||
with open(daily_file) as f:
|
||||
daily_lines = [json.loads(line) for line in f if line.strip()]
|
||||
assert len(daily_lines) == 2
|
||||
assert daily_lines[0]["calendarDate"] == "2026-10-01"
|
||||
assert daily_lines[1]["calendarDate"] == "2026-10-02"
|
||||
|
||||
# Re-syncing 2026-10-01 must not create duplicates (idempotent upsert)
|
||||
sync_date_range(client, writer, ["2026-10-01"])
|
||||
|
||||
with open(daily_file) as f:
|
||||
daily_lines_after = [json.loads(line) for line in f if line.strip()]
|
||||
assert len(daily_lines_after) == 2
|
||||
|
||||
# Check activities
|
||||
with open(activities_file) as f:
|
||||
act_lines = [json.loads(line) for line in f if line.strip()]
|
||||
# Mock returns activityId 987654321 for each day, so deduplication keeps 1 record with latest info
|
||||
assert len(act_lines) == 1
|
||||
assert act_lines[0]["activityId"] == 987654321
|
||||
|
||||
|
||||
def test_generate_date_list():
|
||||
dates = generate_date_list("2026-09-28", "2026-10-01")
|
||||
assert dates == ["2026-09-28", "2026-09-29", "2026-09-30", "2026-10-01"]
|
||||
|
||||
# Reversed dates should automatically normalize
|
||||
dates_rev = generate_date_list("2026-10-01", "2026-09-28")
|
||||
assert dates_rev == ["2026-09-28", "2026-09-29", "2026-09-30", "2026-10-01"]
|
||||
|
||||
|
||||
def test_garmin_auth_error_handling(temp_data_dir):
|
||||
# Non-mock client with empty dir should raise FileNotFoundError
|
||||
with pytest.raises(FileNotFoundError):
|
||||
GarminSyncClient(token_dir=temp_data_dir, mock=False)
|
||||
Reference in New Issue
Block a user