Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion host_agy_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def read_bytes():
return os.read(master_fd, 1024)
except OSError:
return b""

while True:
data = await loop_ref.run_in_executor(None, read_bytes)
if not data:
Expand Down
16 changes: 8 additions & 8 deletions pod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ spec:
- name: CLICKHOUSE_USER
value: clickhouse
- name: CLICKHOUSE_PASSWORD
value: clickhouse
value: CLICKHOUSE_PASSWORD_PLACEHOLDER
image: docker.io/clickhouse/clickhouse-server:26.5.1
livenessProbe:
exec:
Expand All @@ -196,7 +196,7 @@ spec:
- --user
- clickhouse
- --password
- clickhouse
- CLICKHOUSE_PASSWORD_PLACEHOLDER
- --query
- SELECT 1
initialDelaySeconds: 20
Expand All @@ -222,7 +222,7 @@ spec:
- --port
- '6380'
- --requirepass
- langfuse-redis-2026
- REDIS_AUTH_PLACEHOLDER
- --maxmemory-policy
- noeviction
- --loglevel
Expand All @@ -242,7 +242,7 @@ spec:
- -p
- "6380"
- -a
- langfuse-redis-2026
- REDIS_AUTH_PLACEHOLDER
- ping
initialDelaySeconds: 2
periodSeconds: 5
Expand Down Expand Up @@ -287,15 +287,15 @@ spec:
- name: CLICKHOUSE_USER
value: clickhouse
- name: CLICKHOUSE_PASSWORD
value: clickhouse
value: CLICKHOUSE_PASSWORD_PLACEHOLDER
- name: CLICKHOUSE_CLUSTER_ENABLED
value: 'false'
- name: REDIS_HOST
value: 127.0.0.1
- name: REDIS_PORT
value: '6380'
- name: REDIS_AUTH
value: langfuse-redis-2026
value: REDIS_AUTH_PLACEHOLDER
- name: LANGFUSE_INIT_ORG_ID
value: org-local-dev-id
- name: LANGFUSE_INIT_ORG_NAME
Expand Down Expand Up @@ -343,15 +343,15 @@ spec:
- name: CLICKHOUSE_USER
value: clickhouse
- name: CLICKHOUSE_PASSWORD
value: clickhouse
value: CLICKHOUSE_PASSWORD_PLACEHOLDER
- name: CLICKHOUSE_CLUSTER_ENABLED
value: 'false'
- name: REDIS_HOST
value: 127.0.0.1
- name: REDIS_PORT
value: '6380'
- name: REDIS_AUTH
value: langfuse-redis-2026
value: REDIS_AUTH_PLACEHOLDER
- name: HOSTNAME
value: 0.0.0.0
- name: PORT
Expand Down
90 changes: 90 additions & 0 deletions router/tests/test_get_gemini_oauth_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import pytest
import os
import sys
import json
from unittest.mock import patch, mock_open

# Ensure router directory is in sys.path
router_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if router_path not in sys.path:
sys.path.insert(0, router_path)

import main
Comment on lines +8 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Importing main directly at the module level will trigger the configuration loading logic in router/main.py (lines 261-267). If /config/config.yaml does not exist in the environment where the tests are run (which is common in local development or CI environments), main.py will call sys.exit(1). This causes the test runner to crash immediately during test collection.

To prevent this, we can write a minimal dummy configuration to a temporary file and set the CONFIG_PATH environment variable before importing main.

import tempfile

# Ensure router directory is in sys.path
router_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if router_path not in sys.path:
    sys.path.insert(0, router_path)

# Create a temporary config file to prevent main.py from calling sys.exit(1) on import
dummy_config = """
server:
  host: "127.0.0.1"
  port: 5000
router:
  router_model:
    api_base: "http://127.0.0.1:8080/v1"
    api_key: "local-token"
    model: "qwen-0.8b-routing"
classification_rules:
  system_prompt: ""
backends: []
"""

with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as tmp:
    tmp.write(dummy_config)
    tmp_path = tmp.name

os.environ["CONFIG_PATH"] = tmp_path

try:
    import main
finally:
    try:
        os.unlink(tmp_path)
    except OSError:
        pass


def test_get_gemini_oauth_status_missing_file():
with patch("os.path.exists", return_value=False):
result = main.get_gemini_oauth_status()
assert result == {"status": "missing", "detail": "No oauth_creds.json found", "expiry_ms": 0}

def test_get_gemini_oauth_status_no_access_token():
mock_data = {"expiry_date": 1234567890000}
with patch("os.path.exists", return_value=True), \
patch("builtins.open", mock_open(read_data=json.dumps(mock_data))):
result = main.get_gemini_oauth_status()
assert result == {"status": "missing", "detail": "No access token in file", "expiry_ms": 0}

def test_get_gemini_oauth_status_valid_less_than_60s():
current_time_ms = 1000000000000
expiry_ms = current_time_ms + 45000 # 45 seconds from now
mock_data = {"access_token": "test_token", "expiry_date": expiry_ms}
with patch("os.path.exists", return_value=True), \
patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \
patch("time.time", return_value=current_time_ms / 1000.0):
result = main.get_gemini_oauth_status()
assert result == {"status": "valid", "detail": "Expires in 45s", "expiry_ms": expiry_ms}

def test_get_gemini_oauth_status_valid_less_than_3600s():
current_time_ms = 1000000000000
expiry_ms = current_time_ms + 1500000 # 25 minutes from now (25 * 60 = 1500 seconds)
mock_data = {"access_token": "test_token", "expiry_date": expiry_ms}
with patch("os.path.exists", return_value=True), \
patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \
patch("time.time", return_value=current_time_ms / 1000.0):
result = main.get_gemini_oauth_status()
assert result == {"status": "valid", "detail": "Expires in 25m 0s", "expiry_ms": expiry_ms}

def test_get_gemini_oauth_status_valid_more_than_3600s():
current_time_ms = 1000000000000
expiry_ms = current_time_ms + 7500000 # 2 hours, 5 minutes from now (2 * 3600 + 5 * 60 = 7500)
mock_data = {"access_token": "test_token", "expiry_date": expiry_ms}
with patch("os.path.exists", return_value=True), \
patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \
patch("time.time", return_value=current_time_ms / 1000.0):
result = main.get_gemini_oauth_status()
assert result == {"status": "valid", "detail": "Expires in 2h 5m", "expiry_ms": expiry_ms}

def test_get_gemini_oauth_status_expired_less_than_3600s():
current_time_ms = 1000000000000
expiry_ms = current_time_ms - 1500000 # Expired 25 minutes ago
mock_data = {"access_token": "test_token", "expiry_date": expiry_ms}
with patch("os.path.exists", return_value=True), \
patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \
patch("time.time", return_value=current_time_ms / 1000.0):
result = main.get_gemini_oauth_status()
assert result == {"status": "expired", "detail": "Expired 25 minutes ago", "expiry_ms": expiry_ms}

def test_get_gemini_oauth_status_expired_less_than_86400s():
current_time_ms = 1000000000000
expiry_ms = current_time_ms - 7500000 # Expired 2 hours ago
mock_data = {"access_token": "test_token", "expiry_date": expiry_ms}
with patch("os.path.exists", return_value=True), \
patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \
patch("time.time", return_value=current_time_ms / 1000.0):
result = main.get_gemini_oauth_status()
assert result == {"status": "expired", "detail": "Expired 2 hours ago", "expiry_ms": expiry_ms}

def test_get_gemini_oauth_status_expired_more_than_86400s():
current_time_ms = 1000000000000
expiry_ms = current_time_ms - 172800000 # Expired 2 days ago (2 * 86400 * 1000 = 172800000)
mock_data = {"access_token": "test_token", "expiry_date": expiry_ms}
with patch("os.path.exists", return_value=True), \
patch("builtins.open", mock_open(read_data=json.dumps(mock_data))), \
patch("time.time", return_value=current_time_ms / 1000.0):
result = main.get_gemini_oauth_status()
assert result == {"status": "expired", "detail": "Expired 2 days ago", "expiry_ms": expiry_ms}

def test_get_gemini_oauth_status_exception():
with patch("os.path.exists", return_value=True), \
patch("builtins.open", side_effect=Exception("Test error")):
result = main.get_gemini_oauth_status()
assert result == {"status": "error", "detail": "Test error", "expiry_ms": 0}
46 changes: 0 additions & 46 deletions router/tests/test_get_goose_sessions.py

This file was deleted.

61 changes: 0 additions & 61 deletions router/tests/test_load_persisted_stats.py

This file was deleted.

22 changes: 19 additions & 3 deletions start-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ if [ -z "$LANGFUSE_INIT_USER_PASSWORD" ]; then
echo "βœ“ Generated new LANGFUSE_INIT_USER_PASSWORD and saved to $ENV_FILE"
fi

if [ -z "$REDIS_AUTH" ]; then
REDIS_AUTH="$(openssl rand -hex 16)"
echo "REDIS_AUTH=\"$REDIS_AUTH\"" >> "$ENV_FILE"
echo "βœ“ Generated new REDIS_AUTH and saved to $ENV_FILE"
fi

if [ -z "$CLICKHOUSE_PASSWORD" ]; then
CLICKHOUSE_PASSWORD="$(openssl rand -hex 16)"
echo "CLICKHOUSE_PASSWORD=\"$CLICKHOUSE_PASSWORD\"" >> "$ENV_FILE"
echo "βœ“ Generated new CLICKHOUSE_PASSWORD and saved to $ENV_FILE"
fi


if [ -z "$ROUTER_API_KEY" ]; then
ROUTER_API_KEY="$(openssl rand -hex 32)"
Expand Down Expand Up @@ -370,7 +382,7 @@ if podman pod exists agent-router-pod 2>/dev/null; then
fi

render_pod_yaml() {
export WORKDIR HOME LITELLM_MASTER_KEY POSTGRES_PASSWORD NEXTAUTH_SECRET SALT ENCRYPTION_KEY LANGFUSE_INIT_USER_PASSWORD MINIO_ROOT_USER MINIO_ROOT_PASSWORD
export WORKDIR HOME LITELLM_MASTER_KEY POSTGRES_PASSWORD NEXTAUTH_SECRET SALT ENCRYPTION_KEY LANGFUSE_INIT_USER_PASSWORD MINIO_ROOT_USER MINIO_ROOT_PASSWORD REDIS_AUTH CLICKHOUSE_PASSWORD
python3 - "$WORKDIR/pod.yaml" <<'PY'
import os, sys, urllib.parse
uid = os.getuid()
Expand All @@ -387,8 +399,10 @@ placeholders = [
"ENCRYPTION_KEY_PLACEHOLDER",
"postgres-password-***",
"MINIO_USER_PLACEHOLDER",
"MINIO_PASSWORD_PLACEHOLDER"
"LANGFUSE_INIT_USER_PASSWORD_PLACEHOLDER"
"MINIO_PASSWORD_PLACEHOLDER",
"LANGFUSE_INIT_USER_PASSWORD_PLACEHOLDER",
"REDIS_AUTH_PLACEHOLDER",
"CLICKHOUSE_PASSWORD_PLACEHOLDER"
]
for ph in placeholders:
if ph not in text:
Expand All @@ -408,6 +422,8 @@ text = text.replace("ENCRYPTION_KEY_PLACEHOLDER", os.environ["ENCRYPTION_KEY"])
text = text.replace("MINIO_USER_PLACEHOLDER", os.environ["MINIO_ROOT_USER"])
text = text.replace("MINIO_PASSWORD_PLACEHOLDER", os.environ["MINIO_ROOT_PASSWORD"])
text = text.replace("LANGFUSE_INIT_USER_PASSWORD_PLACEHOLDER", os.environ["LANGFUSE_INIT_USER_PASSWORD"])
text = text.replace("REDIS_AUTH_PLACEHOLDER", os.environ["REDIS_AUTH"])
text = text.replace("CLICKHOUSE_PASSWORD_PLACEHOLDER", os.environ["CLICKHOUSE_PASSWORD"])
sys.stdout.write(text)
PY
}
Expand Down