2cab53bc94
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers CLI - Convert documentation to AI skills dockerfile:Dockerfile name:skill-seekers]) (push) Waiting to run
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers MCP Server - 25 tools for AI assistants dockerfile:Dockerfile.mcp name:skill-seekers-mcp]) (push) Waiting to run
Docker Publish / Test Docker Images (push) Blocked by required conditions
Test Vector Database Adaptors / Test chroma Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test faiss Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test qdrant Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test weaviate Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test MCP Vector DB Tools (push) Waiting to run
Tests / Code Quality (Ruff & Mypy) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (macos-latest, 3.11) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (macos-latest, 3.12) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.10) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.11) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.12) (push) Waiting to run
Tests / Tests (push) Blocked by required conditions
Tests / Serial / Integration / E2E Tests (push) Blocked by required conditions
Tests / MCP Server Tests (push) Blocked by required conditions
210 lines
7.2 KiB
Python
210 lines
7.2 KiB
Python
"""Tests for sync Pydantic models (models.py)."""
|
|
|
|
from datetime import datetime
|
|
from skill_seekers.sync.models import (
|
|
ChangeType,
|
|
PageChange,
|
|
ChangeReport,
|
|
SyncConfig,
|
|
SyncState,
|
|
WebhookPayload,
|
|
)
|
|
|
|
|
|
class TestChangeType:
|
|
def test_values(self):
|
|
assert ChangeType.ADDED == "added"
|
|
assert ChangeType.MODIFIED == "modified"
|
|
assert ChangeType.DELETED == "deleted"
|
|
assert ChangeType.UNCHANGED == "unchanged"
|
|
|
|
|
|
class TestPageChange:
|
|
def test_creation(self):
|
|
now = datetime.utcnow()
|
|
change = PageChange(
|
|
url="https://example.com/page",
|
|
change_type=ChangeType.MODIFIED,
|
|
old_hash="abc123",
|
|
new_hash="def456",
|
|
diff="@@ -1,3 +1,4 @@",
|
|
detected_at=now,
|
|
)
|
|
assert change.url == "https://example.com/page"
|
|
assert change.change_type == ChangeType.MODIFIED
|
|
assert change.old_hash == "abc123"
|
|
assert change.new_hash == "def456"
|
|
assert change.diff == "@@ -1,3 +1,4 @@"
|
|
|
|
def test_minimal(self):
|
|
change = PageChange(url="https://example.com", change_type=ChangeType.ADDED)
|
|
assert change.url == "https://example.com"
|
|
assert change.change_type == ChangeType.ADDED
|
|
assert change.old_hash is None
|
|
assert change.new_hash is None
|
|
assert change.diff is None
|
|
assert change.detected_at is not None
|
|
|
|
def test_serialization_roundtrip(self):
|
|
change = PageChange(
|
|
url="https://example.com/page",
|
|
change_type=ChangeType.ADDED,
|
|
new_hash="newhash",
|
|
)
|
|
data = change.model_dump()
|
|
restored = PageChange(**data)
|
|
assert restored.url == change.url
|
|
assert restored.change_type == change.change_type
|
|
assert restored.new_hash == change.new_hash
|
|
|
|
|
|
class TestChangeReport:
|
|
def test_creation(self):
|
|
report = ChangeReport(skill_name="test-skill", total_pages=10)
|
|
assert report.skill_name == "test-skill"
|
|
assert report.total_pages == 10
|
|
assert report.added == []
|
|
assert report.modified == []
|
|
assert report.deleted == []
|
|
assert report.unchanged == 0
|
|
|
|
def test_has_changes_true(self):
|
|
report = ChangeReport(
|
|
skill_name="test-skill",
|
|
total_pages=10,
|
|
modified=[
|
|
PageChange(
|
|
url="https://example.com",
|
|
change_type=ChangeType.MODIFIED,
|
|
old_hash="a",
|
|
new_hash="b",
|
|
)
|
|
],
|
|
)
|
|
assert report.has_changes is True
|
|
|
|
def test_has_changes_false(self):
|
|
report = ChangeReport(skill_name="test-skill", total_pages=10, unchanged=10)
|
|
assert report.has_changes is False
|
|
|
|
def test_change_count(self):
|
|
report = ChangeReport(
|
|
skill_name="test-skill",
|
|
total_pages=10,
|
|
added=[PageChange(url="https://a.com", change_type=ChangeType.ADDED)],
|
|
modified=[
|
|
PageChange(
|
|
url="https://b.com", change_type=ChangeType.MODIFIED, old_hash="a", new_hash="b"
|
|
)
|
|
],
|
|
deleted=[PageChange(url="https://c.com", change_type=ChangeType.DELETED)],
|
|
)
|
|
assert report.change_count == 3
|
|
|
|
def test_change_count_zero(self):
|
|
report = ChangeReport(skill_name="test-skill", total_pages=5, unchanged=5)
|
|
assert report.change_count == 0
|
|
|
|
|
|
class TestSyncConfig:
|
|
def test_defaults(self):
|
|
config = SyncConfig(skill_config="configs/test.json")
|
|
assert config.skill_config == "configs/test.json"
|
|
assert config.check_interval == 3600
|
|
assert config.enabled is True
|
|
assert config.auto_update is False
|
|
assert config.notify_on_change is True
|
|
assert config.notification_channels == []
|
|
assert config.webhook_url is None
|
|
assert config.email_recipients == []
|
|
assert config.slack_webhook is None
|
|
|
|
def test_full_config(self):
|
|
config = SyncConfig(
|
|
skill_config="configs/react.json",
|
|
check_interval=1800,
|
|
auto_update=True,
|
|
notification_channels=["slack", "webhook"],
|
|
webhook_url="https://hooks.example.com/webhook",
|
|
slack_webhook="https://hooks.slack.com/services/T000/B000/XXX",
|
|
email_recipients=["dev@example.com"],
|
|
)
|
|
assert config.check_interval == 1800
|
|
assert config.auto_update is True
|
|
assert "slack" in config.notification_channels
|
|
assert config.webhook_url == "https://hooks.example.com/webhook"
|
|
assert "dev@example.com" in config.email_recipients
|
|
|
|
def test_serialization_roundtrip(self):
|
|
config = SyncConfig(
|
|
skill_config="configs/test.json",
|
|
notification_channels=["slack", "email"],
|
|
email_recipients=["a@b.com", "c@d.com"],
|
|
)
|
|
data = config.model_dump()
|
|
restored = SyncConfig(**data)
|
|
assert restored.skill_config == config.skill_config
|
|
assert restored.notification_channels == ["slack", "email"]
|
|
assert len(restored.email_recipients) == 2
|
|
|
|
|
|
class TestSyncState:
|
|
def test_defaults(self):
|
|
state = SyncState(skill_name="test-skill")
|
|
assert state.skill_name == "test-skill"
|
|
assert state.last_check is None
|
|
assert state.last_change is None
|
|
assert state.total_checks == 0
|
|
assert state.total_changes == 0
|
|
assert state.page_hashes == {}
|
|
assert state.status == "idle"
|
|
assert state.error is None
|
|
|
|
def test_active_state(self):
|
|
now = datetime.utcnow()
|
|
state = SyncState(
|
|
skill_name="test-skill",
|
|
last_check=now,
|
|
total_checks=5,
|
|
total_changes=2,
|
|
page_hashes={"https://a.com": "abc123"},
|
|
status="checking",
|
|
)
|
|
assert state.total_checks == 5
|
|
assert state.total_changes == 2
|
|
assert state.status == "checking"
|
|
assert len(state.page_hashes) == 1
|
|
|
|
def test_error_state(self):
|
|
state = SyncState(skill_name="test-skill", status="error", error="Connection refused")
|
|
assert state.status == "error"
|
|
assert state.error == "Connection refused"
|
|
|
|
|
|
class TestWebhookPayload:
|
|
def test_creation(self):
|
|
payload = WebhookPayload(
|
|
event="change_detected",
|
|
skill_name="test-skill",
|
|
metadata={"source": "periodic_check"},
|
|
)
|
|
assert payload.event == "change_detected"
|
|
assert payload.skill_name == "test-skill"
|
|
assert payload.metadata == {"source": "periodic_check"}
|
|
assert payload.timestamp is not None
|
|
assert payload.changes is None
|
|
|
|
def test_with_changes(self):
|
|
report = ChangeReport(
|
|
skill_name="test-skill",
|
|
total_pages=10,
|
|
modified=[
|
|
PageChange(
|
|
url="https://a.com", change_type=ChangeType.MODIFIED, old_hash="a", new_hash="b"
|
|
)
|
|
],
|
|
)
|
|
payload = WebhookPayload(event="sync_complete", skill_name="test-skill", changes=report)
|
|
assert payload.changes == report
|
|
assert payload.changes.has_changes is True
|