mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-07 13:00:22 -05:00
e4789cc26e
Implement comprehensive analytics and monitoring system with PostHog integration, complete observability stack (Prometheus, Grafana, Loki, Promtail), and CI/CD workflows for automated builds. Features: - Add PostHog telemetry integration with privacy-focused event tracking - Implement installation flow for opt-in telemetry configuration - Add telemetry management UI in admin panel with detailed transparency - Track key user events across all major features (projects, tasks, timer, etc.) Infrastructure: - Set up Prometheus for metrics collection - Configure Grafana for visualization dashboards - Integrate Loki and Promtail for log aggregation - Add separate analytics docker-compose configuration CI/CD: - Add GitHub Actions workflows for building and publishing Docker images - Implement separate dev and production build pipelines - Configure automated image publishing to registry Documentation: - Restructure documentation into organized docs/ directory - Add comprehensive guides for telemetry, analytics, and local development - Create transparency documentation for tracked events - Add CI/CD and build configuration guides Code improvements: - Integrate telemetry hooks across all route handlers - Add feature flags and configuration management - Refactor test suite for analytics functionality - Clean up root directory by moving docs and removing test artifacts Breaking changes: - Requires new environment variables for PostHog configuration - Docker compose setup now supports analytics stack Changes: 73 files changed, 955 insertions(+), 14126 deletions(-)
168 lines
6.2 KiB
Python
168 lines
6.2 KiB
Python
"""
|
|
Tests for installation configuration and setup
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import pytest
|
|
from app.utils.installation import InstallationConfig, get_installation_config
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_config_dir(tmp_path):
|
|
"""Create a temporary config directory"""
|
|
config_dir = tmp_path / "data"
|
|
config_dir.mkdir()
|
|
return str(config_dir)
|
|
|
|
|
|
@pytest.fixture
|
|
def installation_config(temp_config_dir, monkeypatch):
|
|
"""Create an InstallationConfig instance with temporary directory"""
|
|
monkeypatch.setattr('app.utils.installation.InstallationConfig.CONFIG_DIR', temp_config_dir)
|
|
config = InstallationConfig()
|
|
return config
|
|
|
|
|
|
class TestInstallationConfig:
|
|
"""Test installation configuration management"""
|
|
|
|
def test_installation_salt_generation(self, installation_config):
|
|
"""Test that installation salt is generated and persisted"""
|
|
# First call should generate salt
|
|
salt1 = installation_config.get_installation_salt()
|
|
assert salt1 is not None
|
|
assert len(salt1) == 64 # 32 bytes = 64 hex chars
|
|
|
|
# Second call should return same salt
|
|
salt2 = installation_config.get_installation_salt()
|
|
assert salt1 == salt2
|
|
|
|
def test_installation_id_generation(self, installation_config):
|
|
"""Test that installation ID is generated and persisted"""
|
|
# First call should generate ID
|
|
id1 = installation_config.get_installation_id()
|
|
assert id1 is not None
|
|
assert len(id1) == 16
|
|
|
|
# Second call should return same ID
|
|
id2 = installation_config.get_installation_id()
|
|
assert id1 == id2
|
|
|
|
def test_installation_id_uniqueness(self, temp_config_dir, monkeypatch):
|
|
"""Test that each installation gets a unique ID"""
|
|
monkeypatch.setattr('app.utils.installation.InstallationConfig.CONFIG_DIR', temp_config_dir)
|
|
|
|
config1 = InstallationConfig()
|
|
id1 = config1.get_installation_id()
|
|
|
|
# Create a new instance (simulating restart)
|
|
config2 = InstallationConfig()
|
|
id2 = config2.get_installation_id()
|
|
|
|
# Should be the same ID (persisted)
|
|
assert id1 == id2
|
|
|
|
def test_setup_completion(self, installation_config):
|
|
"""Test setup completion tracking"""
|
|
# Initially not complete
|
|
assert not installation_config.is_setup_complete()
|
|
|
|
# Mark as complete
|
|
installation_config.mark_setup_complete(telemetry_enabled=True)
|
|
assert installation_config.is_setup_complete()
|
|
assert installation_config.get_telemetry_preference() is True
|
|
|
|
# Verify persistence
|
|
config2 = InstallationConfig()
|
|
assert config2.is_setup_complete()
|
|
assert config2.get_telemetry_preference() is True
|
|
|
|
def test_telemetry_preference(self, installation_config):
|
|
"""Test telemetry preference management"""
|
|
# Default is False
|
|
assert installation_config.get_telemetry_preference() is False
|
|
|
|
# Enable telemetry
|
|
installation_config.set_telemetry_preference(True)
|
|
assert installation_config.get_telemetry_preference() is True
|
|
|
|
# Disable telemetry
|
|
installation_config.set_telemetry_preference(False)
|
|
assert installation_config.get_telemetry_preference() is False
|
|
|
|
def test_config_persistence(self, installation_config, temp_config_dir):
|
|
"""Test that configuration is persisted to disk"""
|
|
# Set some values
|
|
salt = installation_config.get_installation_salt()
|
|
installation_id = installation_config.get_installation_id()
|
|
installation_config.mark_setup_complete(telemetry_enabled=True)
|
|
|
|
# Read the file directly
|
|
config_path = os.path.join(temp_config_dir, 'installation.json')
|
|
assert os.path.exists(config_path)
|
|
|
|
with open(config_path, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
assert data['telemetry_salt'] == salt
|
|
assert data['installation_id'] == installation_id
|
|
assert data['setup_complete'] is True
|
|
assert data['telemetry_enabled'] is True
|
|
|
|
def test_get_all_config(self, installation_config):
|
|
"""Test retrieving all configuration"""
|
|
installation_config.mark_setup_complete(telemetry_enabled=True)
|
|
|
|
config = installation_config.get_all_config()
|
|
assert 'telemetry_salt' in config
|
|
assert 'installation_id' in config
|
|
assert 'setup_complete' in config
|
|
assert config['setup_complete'] is True
|
|
|
|
|
|
class TestSetupRoutes:
|
|
"""Test setup routes"""
|
|
|
|
def test_setup_page_redirects_if_complete(self, client, installation_config):
|
|
"""Test that setup page redirects if setup is already complete"""
|
|
# Mark setup as complete
|
|
installation_config.mark_setup_complete(telemetry_enabled=False)
|
|
|
|
# Try to access setup page
|
|
response = client.get('/setup')
|
|
assert response.status_code in [302, 200] # May redirect or show page
|
|
|
|
def test_setup_completion_flow(self, client, installation_config):
|
|
"""Test completing the setup"""
|
|
# Ensure setup is not complete
|
|
assert not installation_config.is_setup_complete()
|
|
|
|
# Access setup page
|
|
response = client.get('/setup')
|
|
assert response.status_code == 200
|
|
|
|
# Complete setup with telemetry enabled
|
|
response = client.post('/setup', data={
|
|
'telemetry_enabled': 'on'
|
|
}, follow_redirects=False)
|
|
|
|
# Should redirect after completion
|
|
assert response.status_code == 302
|
|
|
|
# Verify setup is complete
|
|
assert installation_config.is_setup_complete()
|
|
assert installation_config.get_telemetry_preference() is True
|
|
|
|
def test_setup_without_telemetry(self, client, installation_config):
|
|
"""Test completing setup with telemetry disabled"""
|
|
# Complete setup without telemetry
|
|
response = client.post('/setup', data={}, follow_redirects=False)
|
|
|
|
# Should redirect after completion
|
|
assert response.status_code == 302
|
|
|
|
# Verify telemetry is disabled
|
|
assert installation_config.get_telemetry_preference() is False
|
|
|