Update.
Build / build-linux (push) Failing after 22s
Build / build-clang (push) Failing after 1m32s
Tests / test (push) Failing after 1m47s
Build / build-macos (push) Has been cancelled

This commit is contained in:
2025-11-29 00:50:53 +01:00
parent a8f1d81976
commit 1ad8901f3e
57 changed files with 18606 additions and 93 deletions
+53
View File
@@ -0,0 +1,53 @@
CC ?= gcc
CFLAGS ?= -Wall -Wextra -pedantic -std=c11 -O2
CFLAGS += -I../src/libtikker/include -I../src/third_party -Iunit
LDFLAGS := -L../build/lib -ltikker -lsqlite3 -lm
UNIT_TESTS := $(wildcard unit/test_*.c)
UNIT_TARGETS := $(UNIT_TESTS:unit/test_%.c=unit/test_%)
INTEGRATION_TESTS := $(wildcard integration/test_*.c)
INTEGRATION_TARGETS := $(INTEGRATION_TESTS:integration/test_%.c=integration/test_%)
.PHONY: test unit integration clean help
test: unit integration
@echo "✓ All tests completed"
unit: $(UNIT_TARGETS)
@echo "Running unit tests..."
@for test in $(UNIT_TARGETS); do \
if [ -f $$test ]; then \
$$test || exit 1; \
fi; \
done
@echo "✓ Unit tests passed"
integration: $(INTEGRATION_TARGETS)
@echo "Running integration tests..."
@for test in $(INTEGRATION_TARGETS); do \
if [ -f $$test ]; then \
$$test || exit 1; \
fi; \
done
@echo "✓ Integration tests passed"
unit/test_%: unit/test_%.c
@echo "Building test: $@"
@$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
integration/test_%: integration/test_%.c
@echo "Building integration test: $@"
@$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
clean:
@rm -f unit/test_*
@rm -f integration/test_*
@find . -name "*.o" -delete
@echo "✓ Tests cleaned"
help:
@echo "Test suite targets:"
@echo " make test - Run all tests"
@echo " make unit - Run unit tests"
@echo " make integration - Run integration tests"
@echo " make clean - Remove test artifacts"
+1
View File
@@ -0,0 +1 @@
"""Tests package for Tikker services."""
+70
View File
@@ -0,0 +1,70 @@
"""
Pytest Configuration for Service Tests
Provides fixtures and configuration for integration testing.
"""
import sys
from pathlib import Path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "src" / "api"))
import pytest
from fastapi.testclient import TestClient
@pytest.fixture(scope="session")
def test_config():
"""Provide test configuration."""
return {
"api_host": "http://localhost:8000",
"ai_host": "http://localhost:8001",
"viz_host": "http://localhost:8002",
"ml_host": "http://localhost:8003",
"timeout": 30
}
@pytest.fixture
def api_client():
"""Create API test client."""
try:
from api_c_integration import app
return TestClient(app)
except Exception as e:
print(f"Warning: Could not load API: {e}")
return None
@pytest.fixture
def ai_client():
"""Create AI service test client."""
try:
from ai_service import app
return TestClient(app)
except Exception as e:
print(f"Warning: Could not load AI service: {e}")
return None
@pytest.fixture
def viz_client():
"""Create visualization service test client."""
try:
from viz_service import app
return TestClient(app)
except Exception as e:
print(f"Warning: Could not load visualization service: {e}")
return None
@pytest.fixture
def ml_client():
"""Create ML service test client."""
try:
from ml_service import app
return TestClient(app)
except Exception as e:
print(f"Warning: Could not load ML service: {e}")
return None
+102
View File
@@ -0,0 +1,102 @@
#!/bin/bash
BUILD_BIN="/home/retoor/projects/tikker/build/bin"
echo "=== Tikker CLI Tools Integration Tests ==="
echo
passed=0
failed=0
# Test 1: Decoder help
echo -n "Testing decoder --help... "
if $BUILD_BIN/tikker-decoder --help 2>&1 | grep -q "Usage:"; then
echo "✓ PASS"
((passed++))
else
echo "✗ FAIL"
((failed++))
fi
# Test 2: Indexer help
echo -n "Testing indexer --help... "
if $BUILD_BIN/tikker-indexer --help 2>&1 | grep -q "Usage:"; then
echo "✓ PASS"
((passed++))
else
echo "✗ FAIL"
((failed++))
fi
# Test 3: Aggregator help
echo -n "Testing aggregator --help... "
if $BUILD_BIN/tikker-aggregator --help 2>&1 | grep -q "Usage:"; then
echo "✓ PASS"
((passed++))
else
echo "✗ FAIL"
((failed++))
fi
# Test 4: Report help
echo -n "Testing report --help... "
if $BUILD_BIN/tikker-report --help 2>&1 | grep -q "Usage:"; then
echo "✓ PASS"
((passed++))
else
echo "✗ FAIL"
((failed++))
fi
# Test 5: Decoder exists and is executable
echo -n "Testing decoder binary... "
if [ -x $BUILD_BIN/tikker-decoder ]; then
echo "✓ PASS"
((passed++))
else
echo "✗ FAIL"
((failed++))
fi
# Test 6: Indexer exists and is executable
echo -n "Testing indexer binary... "
if [ -x $BUILD_BIN/tikker-indexer ]; then
echo "✓ PASS"
((passed++))
else
echo "✗ FAIL"
((failed++))
fi
# Test 7: Aggregator exists and is executable
echo -n "Testing aggregator binary... "
if [ -x $BUILD_BIN/tikker-aggregator ]; then
echo "✓ PASS"
((passed++))
else
echo "✗ FAIL"
((failed++))
fi
# Test 8: Report exists and is executable
echo -n "Testing report binary... "
if [ -x $BUILD_BIN/tikker-report ]; then
echo "✓ PASS"
((passed++))
else
echo "✗ FAIL"
((failed++))
fi
echo
echo "=== Test Summary ==="
echo "Passed: $passed"
echo "Failed: $failed"
if [ $failed -eq 0 ]; then
echo "✓ All tests passed!"
exit 0
else
echo "✗ Some tests failed"
exit 1
fi
+416
View File
@@ -0,0 +1,416 @@
"""
ML Service Tests
Tests for machine learning analytics endpoints.
Covers pattern detection, anomaly detection, and behavioral analysis.
"""
import pytest
from typing import List, Dict, Any
class TestMLServiceHealth:
"""Tests for ML service health and basic functionality."""
def test_ml_health_check(self, ml_client):
"""Test ML service health check endpoint."""
if not ml_client:
pytest.skip("ML client not available")
response = ml_client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert "ml_available" in data
def test_ml_root_endpoint(self, ml_client):
"""Test ML service root endpoint."""
if not ml_client:
pytest.skip("ML client not available")
response = ml_client.get("/")
assert response.status_code == 200
data = response.json()
assert data["name"] == "Tikker ML Service"
assert "endpoints" in data
class TestPatternDetection:
"""Tests for keystroke pattern detection."""
@staticmethod
def _create_keystroke_events(count: int = 100, wpm: float = 50) -> List[Dict]:
"""Create mock keystroke events."""
events = []
interval = int((60000 / (wpm * 5)))
for i in range(count):
events.append({
"timestamp": i * interval,
"key_code": 65 + (i % 26),
"event_type": "press"
})
return events
def test_detect_fast_typing_pattern(self, ml_client):
"""Test detection of fast typing pattern."""
if not ml_client:
pytest.skip("ML client not available")
fast_events = self._create_keystroke_events(count=150, wpm=80)
payload = {
"events": fast_events,
"user_id": "test_user"
}
response = ml_client.post("/patterns/detect", json=payload)
if response.status_code == 200:
data = response.json()
assert isinstance(data, list)
pattern_names = [p["name"] for p in data]
assert any("fast" in name for name in pattern_names)
def test_detect_slow_typing_pattern(self, ml_client):
"""Test detection of slow typing pattern."""
if not ml_client:
pytest.skip("ML client not available")
slow_events = self._create_keystroke_events(count=50, wpm=20)
payload = {
"events": slow_events,
"user_id": "test_user"
}
response = ml_client.post("/patterns/detect", json=payload)
if response.status_code == 200:
data = response.json()
pattern_names = [p["name"] for p in data]
assert any("slow" in name for name in pattern_names)
def test_pattern_detection_empty_events(self, ml_client):
"""Test pattern detection with empty events."""
if not ml_client:
pytest.skip("ML client not available")
payload = {
"events": [],
"user_id": "test_user"
}
response = ml_client.post("/patterns/detect", json=payload)
assert response.status_code == 400
class TestAnomalyDetection:
"""Tests for keystroke anomaly detection."""
@staticmethod
def _create_keystroke_events(count: int = 100, wpm: float = 50) -> List[Dict]:
"""Create mock keystroke events."""
events = []
interval = int((60000 / (wpm * 5)))
for i in range(count):
events.append({
"timestamp": i * interval,
"key_code": 65 + (i % 26),
"event_type": "press"
})
return events
def test_detect_typing_speed_anomaly(self, ml_client):
"""Test detection of typing speed anomaly."""
if not ml_client:
pytest.skip("ML client not available")
normal_events = self._create_keystroke_events(count=100, wpm=50)
payload = {
"events": normal_events,
"user_id": "test_user_anom"
}
response = ml_client.post("/anomalies/detect", json=payload)
if response.status_code == 200:
data = response.json()
assert isinstance(data, list)
def test_anomaly_detection_empty_events(self, ml_client):
"""Test anomaly detection with empty events."""
if not ml_client:
pytest.skip("ML client not available")
payload = {
"events": [],
"user_id": "test_user"
}
response = ml_client.post("/anomalies/detect", json=payload)
assert response.status_code == 400
class TestBehavioralProfile:
"""Tests for behavioral profile building."""
@staticmethod
def _create_keystroke_events(count: int = 200) -> List[Dict]:
"""Create mock keystroke events."""
events = []
for i in range(count):
events.append({
"timestamp": i * 100,
"key_code": 65 + (i % 26),
"event_type": "press"
})
return events
def test_build_behavioral_profile(self, ml_client):
"""Test building behavioral profile from events."""
if not ml_client:
pytest.skip("ML client not available")
events = self._create_keystroke_events(count=200)
payload = {
"events": events,
"user_id": "profile_test_user"
}
response = ml_client.post("/profile/build", json=payload)
if response.status_code == 200:
data = response.json()
assert "user_id" in data
assert "avg_typing_speed" in data
assert "peak_hours" in data
assert "common_words" in data
assert "consistency_score" in data
assert "patterns" in data
assert data["user_id"] == "profile_test_user"
assert data["consistency_score"] >= 0
assert data["consistency_score"] <= 1
def test_profile_empty_events(self, ml_client):
"""Test profile building with empty events."""
if not ml_client:
pytest.skip("ML client not available")
payload = {
"events": [],
"user_id": "test_user"
}
response = ml_client.post("/profile/build", json=payload)
assert response.status_code == 400
class TestAuthenticityCheck:
"""Tests for user authenticity verification."""
@staticmethod
def _create_keystroke_events(count: int = 100, wpm: float = 50) -> List[Dict]:
"""Create mock keystroke events."""
events = []
interval = int((60000 / (wpm * 5)))
for i in range(count):
events.append({
"timestamp": i * interval,
"key_code": 65 + (i % 26),
"event_type": "press"
})
return events
def test_authenticity_check_unknown_user(self, ml_client):
"""Test authenticity check for unknown user."""
if not ml_client:
pytest.skip("ML client not available")
events = self._create_keystroke_events(count=100)
payload = {
"events": events,
"user_id": "unknown_user_123"
}
response = ml_client.post("/authenticity/check", json=payload)
if response.status_code == 200:
data = response.json()
assert "authenticity_score" in data
assert "verdict" in data
assert data["verdict"] == "unknown"
def test_authenticity_check_established_user(self, ml_client):
"""Test authenticity check for user with established profile."""
if not ml_client:
pytest.skip("ML client not available")
user_id = "established_user_test"
events = self._create_keystroke_events(count=100, wpm=50)
build_payload = {
"events": events,
"user_id": user_id
}
build_response = ml_client.post("/profile/build", json=build_payload)
if build_response.status_code == 200:
check_payload = {
"events": events,
"user_id": user_id
}
check_response = ml_client.post("/authenticity/check", json=check_payload)
if check_response.status_code == 200:
data = check_response.json()
assert "authenticity_score" in data
assert "verdict" in data
class TestTemporalAnalysis:
"""Tests for temporal pattern analysis."""
def test_temporal_analysis_default_range(self, ml_client):
"""Test temporal analysis with default date range."""
if not ml_client:
pytest.skip("ML client not available")
payload = {"date_range_days": 7}
response = ml_client.post("/temporal/analyze", json=payload)
if response.status_code == 200:
data = response.json()
assert "trend" in data
assert "date_range_days" in data or "error" in data
if "date_range_days" in data:
assert data["date_range_days"] == 7
def test_temporal_analysis_custom_range(self, ml_client):
"""Test temporal analysis with custom date range."""
if not ml_client:
pytest.skip("ML client not available")
payload = {"date_range_days": 30}
response = ml_client.post("/temporal/analyze", json=payload)
if response.status_code == 200:
data = response.json()
assert "date_range_days" in data or "error" in data
if "date_range_days" in data:
assert data["date_range_days"] == 30
class TestModelTraining:
"""Tests for ML model training."""
def test_train_model_default(self, ml_client):
"""Test training ML model with default parameters."""
if not ml_client:
pytest.skip("ML client not available")
response = ml_client.post("/model/train")
if response.status_code == 200:
data = response.json()
assert data["status"] == "trained"
assert "samples" in data
assert "features" in data
assert "accuracy" in data
def test_train_model_custom_size(self, ml_client):
"""Test training ML model with custom sample size."""
if not ml_client:
pytest.skip("ML client not available")
response = ml_client.post("/model/train?sample_size=500")
if response.status_code == 200:
data = response.json()
assert data["samples"] == 500
class TestBehaviorPrediction:
"""Tests for behavior prediction."""
@staticmethod
def _create_keystroke_events(count: int = 100) -> List[Dict]:
"""Create mock keystroke events."""
events = []
for i in range(count):
events.append({
"timestamp": i * 100,
"key_code": 65 + (i % 26),
"event_type": "press"
})
return events
def test_predict_behavior_untrained_model(self, ml_client):
"""Test behavior prediction with untrained model."""
if not ml_client:
pytest.skip("ML client not available")
events = self._create_keystroke_events(count=100)
payload = {
"events": events,
"user_id": "test_user"
}
response = ml_client.post("/behavior/predict", json=payload)
if response.status_code == 200:
data = response.json()
assert "behavior_category" in data or "status" in data
def test_predict_behavior_after_training(self, ml_client):
"""Test behavior prediction after model training."""
if not ml_client:
pytest.skip("ML client not available")
train_response = ml_client.post("/model/train?sample_size=100")
if train_response.status_code == 200:
events = self._create_keystroke_events(count=100)
payload = {
"events": events,
"user_id": "test_user"
}
predict_response = ml_client.post("/behavior/predict", json=payload)
if predict_response.status_code == 200:
data = predict_response.json()
assert "behavior_category" in data
assert "confidence" in data
@pytest.fixture
def ml_client():
"""Create ML service test client."""
from fastapi.testclient import TestClient
try:
from ml_service import app
return TestClient(app)
except:
return None
+343
View File
@@ -0,0 +1,343 @@
"""
Performance Testing for Tikker Services
Measures response times, throughput, and resource usage.
Identifies bottlenecks and optimization opportunities.
"""
import pytest
import time
import json
from typing import Dict, List, Tuple
import statistics
class PerformanceMetrics:
"""Collect and analyze performance metrics."""
def __init__(self):
self.measurements: Dict[str, List[float]] = {}
def record(self, name: str, value: float):
"""Record a measurement."""
if name not in self.measurements:
self.measurements[name] = []
self.measurements[name].append(value)
def summary(self, name: str) -> Dict[str, float]:
"""Get summary statistics for measurements."""
if name not in self.measurements:
return {}
values = self.measurements[name]
return {
"count": len(values),
"min": min(values),
"max": max(values),
"avg": statistics.mean(values),
"median": statistics.median(values),
"stdev": statistics.stdev(values) if len(values) > 1 else 0
}
@pytest.fixture
def metrics():
"""Provide metrics collector."""
return PerformanceMetrics()
class TestAPIPerformance:
"""Tests for API performance characteristics."""
def test_health_check_latency(self, api_client, metrics):
"""Measure health check endpoint latency."""
if not api_client:
pytest.skip("API client not available")
for _ in range(10):
start = time.time()
response = api_client.get("/health")
elapsed = (time.time() - start) * 1000
assert response.status_code == 200
metrics.record("health_check_latency", elapsed)
summary = metrics.summary("health_check_latency")
assert summary["avg"] < 100, "Health check should be < 100ms"
assert summary["max"] < 500, "Health check max should be < 500ms"
def test_daily_stats_latency(self, api_client, metrics):
"""Measure daily stats endpoint latency."""
if not api_client:
pytest.skip("API client not available")
for _ in range(5):
start = time.time()
response = api_client.get("/api/stats/daily")
elapsed = (time.time() - start) * 1000
if response.status_code == 200:
metrics.record("daily_stats_latency", elapsed)
if "daily_stats_latency" in metrics.measurements:
summary = metrics.summary("daily_stats_latency")
assert summary["avg"] < 200, "Daily stats should be < 200ms"
def test_top_words_latency(self, api_client, metrics):
"""Measure top words endpoint latency."""
if not api_client:
pytest.skip("API client not available")
for limit in [10, 50, 100]:
for _ in range(3):
start = time.time()
response = api_client.get(f"/api/words/top?limit={limit}")
elapsed = (time.time() - start) * 1000
if response.status_code == 200:
metrics.record(f"top_words_latency_{limit}", elapsed)
for limit in [10, 50, 100]:
key = f"top_words_latency_{limit}"
if key in metrics.measurements:
summary = metrics.summary(key)
assert summary["avg"] < 500, f"Top words (limit={limit}) should be < 500ms"
def test_concurrent_requests(self, api_client, metrics):
"""Test API under concurrent load."""
if not api_client:
pytest.skip("API client not available")
endpoints = [
"/health",
"/api/stats/daily",
"/api/words/top?limit=10"
]
times = []
for endpoint in endpoints:
start = time.time()
response = api_client.get(endpoint)
elapsed = (time.time() - start) * 1000
times.append(elapsed)
if response.status_code == 200:
metrics.record("concurrent_request_latency", elapsed)
avg_time = statistics.mean(times)
assert avg_time < 300, "Average concurrent request latency should be < 300ms"
class TestAIPerformance:
"""Tests for AI service performance."""
def test_health_check_latency(self, ai_client, metrics):
"""Measure AI health check latency."""
if not ai_client:
pytest.skip("AI client not available")
for _ in range(5):
start = time.time()
response = ai_client.get("/health")
elapsed = (time.time() - start) * 1000
assert response.status_code == 200
metrics.record("ai_health_latency", elapsed)
summary = metrics.summary("ai_health_latency")
assert summary["avg"] < 100, "AI health check should be < 100ms"
def test_analysis_latency(self, ai_client, metrics):
"""Measure text analysis latency."""
if not ai_client:
pytest.skip("AI client not available")
payload = {
"text": "This is a test message for analysis of keystroke patterns",
"analysis_type": "general"
}
for _ in range(3):
start = time.time()
response = ai_client.post("/analyze", json=payload)
elapsed = (time.time() - start) * 1000
if response.status_code == 200:
metrics.record("ai_analysis_latency", elapsed)
if "ai_analysis_latency" in metrics.measurements:
summary = metrics.summary("ai_analysis_latency")
print(f"\nAI Analysis latency: {summary}")
class TestVizPerformance:
"""Tests for visualization service performance."""
def test_health_check_latency(self, viz_client, metrics):
"""Measure visualization health check latency."""
if not viz_client:
pytest.skip("Visualization client not available")
for _ in range(5):
start = time.time()
response = viz_client.get("/health")
elapsed = (time.time() - start) * 1000
assert response.status_code == 200
metrics.record("viz_health_latency", elapsed)
summary = metrics.summary("viz_health_latency")
assert summary["avg"] < 100, "Viz health check should be < 100ms"
def test_chart_generation_latency(self, viz_client, metrics):
"""Measure chart generation latency."""
if not viz_client:
pytest.skip("Visualization client not available")
for chart_type in ["bar", "line", "pie"]:
payload = {
"title": f"Test {chart_type} Chart",
"data": {f"Item{i}": i*100 for i in range(10)},
"chart_type": chart_type
}
for _ in range(2):
start = time.time()
response = viz_client.post("/chart", json=payload)
elapsed = (time.time() - start) * 1000
if response.status_code == 200:
metrics.record(f"chart_{chart_type}_latency", elapsed)
for chart_type in ["bar", "line", "pie"]:
key = f"chart_{chart_type}_latency"
if key in metrics.measurements:
summary = metrics.summary(key)
assert summary["avg"] < 1000, f"{chart_type} chart should be < 1000ms"
class TestThroughput:
"""Tests for service throughput."""
def test_sequential_requests(self, api_client):
"""Test sequential request throughput."""
if not api_client:
pytest.skip("API client not available")
start = time.time()
count = 0
while time.time() - start < 5:
response = api_client.get("/health")
if response.status_code == 200:
count += 1
elapsed = time.time() - start
throughput = count / elapsed
print(f"\nSequential throughput: {throughput:.2f} req/s")
assert throughput > 10, "Throughput should be > 10 req/s"
def test_word_search_throughput(self, api_client):
"""Test word search throughput."""
if not api_client:
pytest.skip("API client not available")
words = ["the", "and", "test", "python", "data"]
start = time.time()
count = 0
while time.time() - start < 5:
for word in words:
response = api_client.get(f"/api/words/find?word={word}")
if response.status_code in [200, 404]:
count += 1
elapsed = time.time() - start
throughput = count / elapsed
print(f"\nWord search throughput: {throughput:.2f} req/s")
class TestMemoryUsage:
"""Tests for memory consumption patterns."""
def test_large_data_response(self, api_client):
"""Test API with large data response."""
if not api_client:
pytest.skip("API client not available")
response = api_client.get("/api/words/top?limit=100")
if response.status_code == 200:
data = response.json()
size_mb = len(json.dumps(data)) / (1024 * 1024)
print(f"\nResponse size: {size_mb:.2f} MB")
assert size_mb < 10, "Response should be < 10 MB"
def test_repeated_requests(self, api_client):
"""Test for memory leaks with repeated requests."""
if not api_client:
pytest.skip("API client not available")
for _ in range(100):
response = api_client.get("/health")
assert response.status_code == 200
class TestResponseQuality:
"""Tests for response quality metrics."""
def test_daily_stats_response_structure(self, api_client):
"""Verify daily stats response structure."""
if not api_client:
pytest.skip("API client not available")
response = api_client.get("/api/stats/daily")
if response.status_code == 200:
data = response.json()
required_fields = ["presses", "releases", "repeats", "total"]
for field in required_fields:
assert field in data, f"Missing field: {field}"
def test_top_words_response_structure(self, api_client):
"""Verify top words response structure."""
if not api_client:
pytest.skip("API client not available")
response = api_client.get("/api/words/top?limit=5")
if response.status_code == 200:
data = response.json()
assert isinstance(data, list), "Response should be a list"
if len(data) > 0:
word = data[0]
required_fields = ["rank", "word", "count", "percentage"]
for field in required_fields:
assert field in word, f"Missing field in word: {field}"
class TestErrorRecovery:
"""Tests for error handling and recovery."""
def test_invalid_parameter_handling(self, api_client, metrics):
"""Test handling of invalid parameters."""
if not api_client:
pytest.skip("API client not available")
start = time.time()
response = api_client.get("/api/words/find?word=")
elapsed = (time.time() - start) * 1000
metrics.record("invalid_param_latency", elapsed)
assert response.status_code in [200, 400]
assert elapsed < 100, "Error response should be quick"
def test_missing_required_parameter(self, api_client):
"""Test missing required parameter."""
if not api_client:
pytest.skip("API client not available")
response = api_client.get("/api/stats/hourly")
assert response.status_code in [400, 422, 200]
+307
View File
@@ -0,0 +1,307 @@
"""
Service Integration Tests
Tests for API, AI, and visualization microservices.
Verifies service health, endpoints, and inter-service communication.
"""
import pytest
import json
from typing import Dict, Any
class TestMainAPIService:
"""Tests for main API service with C tools integration."""
def test_api_health_check(self, api_client):
"""Test main API health check endpoint."""
response = api_client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] in ["healthy", "ok"]
assert "tools" in data or "message" in data
def test_api_root_endpoint(self, api_client):
"""Test main API root endpoint."""
response = api_client.get("/")
assert response.status_code == 200
data = response.json()
assert data["name"] == "Tikker API"
assert "version" in data
assert "endpoints" in data
def test_get_daily_stats(self, api_client):
"""Test daily statistics endpoint."""
response = api_client.get("/api/stats/daily")
assert response.status_code in [200, 503]
if response.status_code == 200:
data = response.json()
assert "presses" in data or "status" in data
def test_get_top_words(self, api_client):
"""Test top words endpoint."""
response = api_client.get("/api/words/top?limit=10")
assert response.status_code in [200, 503]
if response.status_code == 200:
data = response.json()
assert isinstance(data, list) or isinstance(data, dict)
def test_decode_file_endpoint(self, api_client):
"""Test file decoding endpoint."""
payload = {
"input_file": "test_input.txt",
"output_file": "test_output.txt",
"verbose": False
}
response = api_client.post("/api/decode", json=payload)
assert response.status_code in [200, 400, 404, 503]
def test_api_health_timeout(self, api_client):
"""Test API health endpoint response time."""
import time
start = time.time()
response = api_client.get("/health")
elapsed = time.time() - start
assert elapsed < 5.0
assert response.status_code == 200
class TestAIService:
"""Tests for AI microservice."""
def test_ai_health_check(self, ai_client):
"""Test AI service health check."""
response = ai_client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert "ai_available" in data
def test_ai_root_endpoint(self, ai_client):
"""Test AI service root endpoint."""
response = ai_client.get("/")
assert response.status_code == 200
data = response.json()
assert data["name"] == "Tikker AI Service"
assert "endpoints" in data
def test_ai_analyze_endpoint(self, ai_client):
"""Test AI text analysis endpoint."""
payload = {
"text": "This is a test message for analysis",
"analysis_type": "general"
}
response = ai_client.post("/analyze", json=payload)
assert response.status_code in [200, 503]
def test_ai_analyze_activity(self, ai_client):
"""Test AI activity analysis."""
payload = {
"text": "typing keyboard input keystroke logs",
"analysis_type": "activity"
}
response = ai_client.post("/analyze", json=payload)
assert response.status_code in [200, 503]
def test_ai_empty_text_validation(self, ai_client):
"""Test AI service rejects empty text."""
payload = {
"text": "",
"analysis_type": "general"
}
response = ai_client.post("/analyze", json=payload)
if response.status_code == 503:
pass
else:
assert response.status_code == 400
class TestVizService:
"""Tests for visualization microservice."""
def test_viz_health_check(self, viz_client):
"""Test visualization service health check."""
response = viz_client.get("/health")
assert response.status_code == 200
data = response.json()
assert data["status"] == "healthy"
assert "viz_available" in data
def test_viz_root_endpoint(self, viz_client):
"""Test visualization service root endpoint."""
response = viz_client.get("/")
assert response.status_code == 200
data = response.json()
assert data["name"] == "Tikker Visualization Service"
assert "supported_charts" in data
def test_viz_bar_chart(self, viz_client):
"""Test bar chart generation."""
payload = {
"title": "Test Bar Chart",
"data": {"A": 10, "B": 20, "C": 15},
"chart_type": "bar",
"width": 10,
"height": 6
}
response = viz_client.post("/chart", json=payload)
assert response.status_code in [200, 503]
if response.status_code == 200:
data = response.json()
assert data["status"] == "success"
assert data["chart_type"] == "bar"
assert "image_base64" in data
def test_viz_line_chart(self, viz_client):
"""Test line chart generation."""
payload = {
"title": "Test Line Chart",
"data": {"Jan": 100, "Feb": 120, "Mar": 140},
"chart_type": "line"
}
response = viz_client.post("/chart", json=payload)
assert response.status_code in [200, 503]
def test_viz_pie_chart(self, viz_client):
"""Test pie chart generation."""
payload = {
"title": "Test Pie Chart",
"data": {"Category1": 30, "Category2": 40, "Category3": 30},
"chart_type": "pie"
}
response = viz_client.post("/chart", json=payload)
assert response.status_code in [200, 503]
def test_viz_chart_download(self, viz_client):
"""Test chart download endpoint."""
payload = {
"title": "Download Test",
"data": {"X": 50, "Y": 75},
"chart_type": "bar"
}
response = viz_client.post("/chart/download", json=payload)
assert response.status_code in [200, 503]
def test_viz_invalid_chart_type(self, viz_client):
"""Test invalid chart type handling."""
payload = {
"title": "Invalid Chart",
"data": {"A": 10},
"chart_type": "invalid"
}
response = viz_client.post("/chart", json=payload)
if response.status_code == 503:
pass
else:
assert response.status_code == 400
class TestServiceIntegration:
"""Tests for service-to-service communication."""
def test_all_services_healthy(self, api_client, ai_client, viz_client):
"""Test all services report healthy status."""
api_response = api_client.get("/health")
ai_response = ai_client.get("/health")
viz_response = viz_client.get("/health")
assert api_response.status_code == 200
assert ai_response.status_code == 200
assert viz_response.status_code == 200
def test_api_to_ai_communication(self, api_client, ai_client):
"""Test API can communicate with AI service."""
api_health = api_client.get("/health")
ai_health = ai_client.get("/health")
assert api_health.status_code == 200
assert ai_health.status_code == 200
def test_api_to_viz_communication(self, api_client, viz_client):
"""Test API can communicate with visualization service."""
api_health = api_client.get("/health")
viz_health = viz_client.get("/health")
assert api_health.status_code == 200
assert viz_health.status_code == 200
def test_concurrent_service_requests(self, api_client, ai_client, viz_client):
"""Test multiple concurrent requests to different services."""
responses = {
"api": api_client.get("/health"),
"ai": ai_client.get("/health"),
"viz": viz_client.get("/health")
}
for service, response in responses.items():
assert response.status_code == 200, f"{service} service failed"
class TestErrorHandling:
"""Tests for error handling and edge cases."""
def test_api_invalid_endpoint(self, api_client):
"""Test API handles invalid endpoints."""
response = api_client.get("/api/invalid")
assert response.status_code == 404
def test_ai_invalid_endpoint(self, ai_client):
"""Test AI service handles invalid endpoints."""
response = ai_client.get("/invalid")
assert response.status_code == 404
def test_viz_invalid_endpoint(self, viz_client):
"""Test visualization service handles invalid endpoints."""
response = viz_client.get("/invalid")
assert response.status_code == 404
def test_api_malformed_json(self, api_client):
"""Test API handles malformed JSON."""
response = api_client.post(
"/api/decode",
content="invalid json",
headers={"Content-Type": "application/json"}
)
assert response.status_code in [400, 422]
def test_ai_malformed_json(self, ai_client):
"""Test AI service handles malformed JSON."""
response = ai_client.post(
"/analyze",
content="invalid json",
headers={"Content-Type": "application/json"}
)
assert response.status_code in [400, 422]
@pytest.fixture
def api_client():
"""Create API test client."""
from fastapi.testclient import TestClient
try:
from api_c_integration import app
return TestClient(app)
except:
return None
@pytest.fixture
def ai_client():
"""Create AI service test client."""
from fastapi.testclient import TestClient
try:
from ai_service import app
return TestClient(app)
except:
return None
@pytest.fixture
def viz_client():
"""Create visualization service test client."""
from fastapi.testclient import TestClient
try:
from viz_service import app
return TestClient(app)
except:
return None
+101
View File
@@ -0,0 +1,101 @@
#ifndef TEST_FRAMEWORK_H
#define TEST_FRAMEWORK_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
typedef struct {
int passed;
int failed;
int total;
const char *current_test;
} test_state_t;
static test_state_t test_state = {0, 0, 0, NULL};
#define TEST_ASSERT(condition, message) \
do { \
if (!(condition)) { \
fprintf(stderr, " ✗ FAIL: %s\n", message); \
test_state.failed++; \
} else { \
test_state.passed++; \
} \
test_state.total++; \
} while(0)
#define ASSERT_EQ(a, b) \
do { \
if ((a) != (b)) { \
fprintf(stderr, " ✗ FAIL: %ld != %ld\n", (long)(a), (long)(b)); \
test_state.failed++; \
} else { \
test_state.passed++; \
} \
test_state.total++; \
} while(0)
#define ASSERT_EQ_STR(a, b) \
do { \
if (strcmp((a), (b)) != 0) { \
fprintf(stderr, " ✗ FAIL: '%s' != '%s'\n", (a), (b)); \
test_state.failed++; \
} else { \
test_state.passed++; \
} \
test_state.total++; \
} while(0)
#define ASSERT_NULL(ptr) \
do { \
if ((ptr) != NULL) { \
fprintf(stderr, " ✗ FAIL: pointer is not NULL\n"); \
test_state.failed++; \
} else { \
test_state.passed++; \
} \
test_state.total++; \
} while(0)
#define ASSERT_NOT_NULL(ptr) \
do { \
if ((ptr) == NULL) { \
fprintf(stderr, " ✗ FAIL: pointer is NULL\n"); \
test_state.failed++; \
} else { \
test_state.passed++; \
} \
test_state.total++; \
} while(0)
#define TEST_BEGIN(name) \
do { \
test_state.current_test = (name); \
printf("TEST: %s\n", (name)); \
} while(0)
#define TEST_END \
do { \
printf("\n"); \
} while(0)
#define TEST_SUMMARY \
do { \
printf("\n========================================\n"); \
printf("Test Summary:\n"); \
printf(" Passed: %d\n", test_state.passed); \
printf(" Failed: %d\n", test_state.failed); \
printf(" Total: %d\n", test_state.total); \
printf("========================================\n"); \
if (test_state.failed > 0) { \
printf("❌ %d test(s) failed\n", test_state.failed); \
return 1; \
} else { \
printf("✓ All tests passed\n"); \
return 0; \
} \
} while(0)
#endif