fleet-memory/hindsight-embed/tests/test_profile_integration.py
Nicolò Boschi 6c7f057e9d
feat(embed): add hindisght-embed profiles (#277)
* feat(embed): add hindisght-embed profiles

* ci: run pytest tests for hindsight-embed in CI

- Add pytest test run step to test-embed job
- This ensures profile tests (37 tests) are run in CI
- Smoke test still runs after pytest tests

* feat(embed): use 'default' profile name consistently

- Configure command now shows "Profile 'default' configured successfully!"
- Profile list shows "default" instead of empty string
- Profile show displays "default" consistently
- All output now uses "default" label for backward-compatible config
- Added port display for default profile in all commands

* fix(embed): replace requests with httpx in profile_manager

- Use httpx.Client() instead of requests.get() for daemon health check
- Update test mock to use httpx.Client instead of requests.get
- Fixes ModuleNotFoundError in CI (requests not in dependencies)
2026-02-02 14:38:06 +01:00

388 lines
13 KiB
Python

"""Integration tests for profile functionality."""
import json
import os
import re
import subprocess
from pathlib import Path
import pytest
def strip_ansi(text):
"""Remove ANSI escape sequences from text."""
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
return ansi_escape.sub("", text)
@pytest.fixture
def temp_home(tmp_path, monkeypatch):
"""Create a temporary home directory for integration tests."""
temp_home = tmp_path / "home"
temp_home.mkdir()
monkeypatch.setenv("HOME", str(temp_home))
return temp_home
@pytest.fixture
def hindsight_embed_cmd():
"""Get the hindsight-embed command."""
return ["uv", "run", "hindsight-embed"]
class TestProfileIntegration:
"""Integration tests for profile workflows."""
def test_create_and_list_profile(self, temp_home, hindsight_embed_cmd):
"""Test creating a profile and listing it."""
# Create profile
result = subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"test-app",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=openai",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test",
"--env",
"HINDSIGHT_EMBED_LLM_MODEL=gpt-4o-mini",
],
capture_output=True,
text=True,
env={**os.environ, "HOME": str(temp_home)},
)
assert result.returncode == 0
assert "Profile 'test-app' configured successfully" in result.stdout
# Verify profile file was created
profile_path = temp_home / ".hindsight" / "profiles" / "test-app.env"
assert profile_path.exists()
config_content = profile_path.read_text()
assert "HINDSIGHT_EMBED_LLM_PROVIDER=openai" in config_content
assert "HINDSIGHT_EMBED_LLM_API_KEY=sk-test" in config_content
# List profiles
result = subprocess.run(
hindsight_embed_cmd + ["profile", "list"],
capture_output=True,
text=True,
env={**os.environ, "HOME": str(temp_home)},
)
assert result.returncode == 0
assert "test-app" in result.stdout
assert "Port:" in result.stdout
def test_profile_show_default(self, temp_home, hindsight_embed_cmd):
"""Test showing the default profile."""
# Create default config
config_dir = temp_home / ".hindsight"
config_dir.mkdir(parents=True, exist_ok=True)
(config_dir / "embed").write_text("KEY=value")
result = subprocess.run(
hindsight_embed_cmd + ["profile", "show"],
capture_output=True,
text=True,
env={**os.environ, "HOME": str(temp_home)},
)
assert result.returncode == 0
output = strip_ansi(result.stdout)
assert "Active profile: default" in output
assert "Source: Default" in output
assert "Port: 8888" in output
def test_profile_show_with_env_var(self, temp_home, hindsight_embed_cmd):
"""Test profile resolution with HINDSIGHT_EMBED_PROFILE env var."""
# Create profile
subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"test-app",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=openai",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test",
],
capture_output=True,
env={**os.environ, "HOME": str(temp_home)},
)
# Show profile with env var
env = {**os.environ, "HOME": str(temp_home), "HINDSIGHT_EMBED_PROFILE": "test-app"}
result = subprocess.run(
hindsight_embed_cmd + ["profile", "show"], capture_output=True, text=True, env=env
)
assert result.returncode == 0
output = strip_ansi(result.stdout)
assert "Active profile: test-app" in output
assert "Source: HINDSIGHT_EMBED_PROFILE environment variable" in output
def test_set_active_profile(self, temp_home, hindsight_embed_cmd):
"""Test setting and using active profile."""
# Create profile
subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"test-app",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=openai",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test",
],
capture_output=True,
env={**os.environ, "HOME": str(temp_home)},
)
# Set as active
result = subprocess.run(
hindsight_embed_cmd + ["profile", "set-active", "test-app"],
capture_output=True,
text=True,
env={**os.environ, "HOME": str(temp_home)},
)
assert result.returncode == 0
output = strip_ansi(result.stdout)
assert "Active profile set to 'test-app'" in output
# Verify active_profile file
active_file = temp_home / ".hindsight" / "active_profile"
assert active_file.exists()
assert active_file.read_text() == "test-app"
# Show profile (should show as active)
result = subprocess.run(
hindsight_embed_cmd + ["profile", "show"],
capture_output=True,
text=True,
env={**os.environ, "HOME": str(temp_home)},
)
assert result.returncode == 0
output = strip_ansi(result.stdout)
assert "Active profile: test-app" in output
def test_delete_profile(self, temp_home, hindsight_embed_cmd):
"""Test deleting a profile."""
# Create profile
subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"test-app",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=openai",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test",
],
capture_output=True,
)
# Delete profile (with 'y' confirmation)
result = subprocess.run(
hindsight_embed_cmd + ["profile", "delete", "test-app"],
input="y\n",
capture_output=True,
text=True,
)
assert result.returncode == 0 or "deleted" in result.stdout.lower()
# Verify profile file is gone
profile_path = temp_home / ".hindsight" / "profiles" / "test-app.env"
# Note: Test may still have file if daemon was running and user cancelled
def test_multiple_profiles_different_ports(self, temp_home, hindsight_embed_cmd):
"""Test that multiple profiles get different ports."""
# Create first profile
subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"app1",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=openai",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test",
],
capture_output=True,
)
# Create second profile
subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"app2",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=openai",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test",
],
capture_output=True,
)
# Read metadata
metadata_path = temp_home / ".hindsight" / "profiles" / "metadata.json"
assert metadata_path.exists()
metadata = json.loads(metadata_path.read_text())
app1_port = metadata["profiles"]["app1"]["port"]
app2_port = metadata["profiles"]["app2"]["port"]
# Ports should be different
assert app1_port != app2_port
assert 8889 <= app1_port <= 9888
assert 8889 <= app2_port <= 9888
def test_profile_validation_fails_for_nonexistent(self, temp_home, hindsight_embed_cmd):
"""Test that using non-existent profile fails."""
# Try to use non-existent profile
env = {**os.environ, "HOME": str(temp_home), "HINDSIGHT_EMBED_PROFILE": "nonexistent"}
result = subprocess.run(
hindsight_embed_cmd + ["profile", "show"], capture_output=True, text=True, env=env
)
assert result.returncode == 1
assert "Profile 'nonexistent' not found" in result.stderr
def test_backward_compatibility_default_profile(self, temp_home, hindsight_embed_cmd):
"""Test that default profile works without any profile commands."""
# Create default config manually (simulating old behavior)
config_dir = temp_home / ".hindsight"
config_dir.mkdir(parents=True, exist_ok=True)
(config_dir / "embed").write_text(
"HINDSIGHT_EMBED_LLM_PROVIDER=openai\n"
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test\n"
"HINDSIGHT_EMBED_LLM_MODEL=gpt-4o-mini\n"
)
# Show profile should work
result = subprocess.run(
hindsight_embed_cmd + ["profile", "show"],
capture_output=True,
text=True,
env={**os.environ, "HOME": str(temp_home)},
)
assert result.returncode == 0
output = strip_ansi(result.stdout)
assert "Active profile: default" in output
assert "Port: 8888" in output
def test_configure_without_profile_flag(self, temp_home, hindsight_embed_cmd):
"""Test that configure without --profile still works (backward compatibility)."""
# Configure without profile flag (should configure default)
# Use non-interactive mode by providing env vars
env = {
**os.environ,
"HOME": str(temp_home),
"HINDSIGHT_EMBED_LLM_PROVIDER": "openai",
"HINDSIGHT_EMBED_LLM_API_KEY": "sk-test",
"HINDSIGHT_EMBED_LLM_MODEL": "gpt-4o-mini",
}
result = subprocess.run(
hindsight_embed_cmd + ["configure"], capture_output=True, text=True, env=env
)
# Should succeed
assert result.returncode == 0
# Verify default config was created
config_path = temp_home / ".hindsight" / "embed"
assert config_path.exists()
def test_clear_active_profile(self, temp_home, hindsight_embed_cmd):
"""Test clearing the active profile."""
# Create and set active profile
subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"test-app",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=openai",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test",
],
capture_output=True,
)
subprocess.run(
hindsight_embed_cmd + ["profile", "set-active", "test-app"],
capture_output=True,
)
# Clear active profile
result = subprocess.run(
hindsight_embed_cmd + ["profile", "set-active", "--none"],
capture_output=True,
text=True,
)
assert result.returncode == 0
assert "Active profile cleared" in result.stdout
# Verify active_profile file is gone
active_file = temp_home / ".hindsight" / "active_profile"
assert not active_file.exists()
def test_profile_port_persistence(self, temp_home, hindsight_embed_cmd):
"""Test that profile port is persistent across recreations."""
# Create profile
subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"test-app",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=openai",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=sk-test",
],
capture_output=True,
)
# Get port
metadata_path = temp_home / ".hindsight" / "profiles" / "metadata.json"
metadata1 = json.loads(metadata_path.read_text())
port1 = metadata1["profiles"]["test-app"]["port"]
# Update profile (recreate with different config)
subprocess.run(
hindsight_embed_cmd
+ [
"configure",
"--profile",
"test-app",
"--env",
"HINDSIGHT_EMBED_LLM_PROVIDER=groq",
"--env",
"HINDSIGHT_EMBED_LLM_API_KEY=gsk-test",
],
capture_output=True,
)
# Get port again
metadata2 = json.loads(metadata_path.read_text())
port2 = metadata2["profiles"]["test-app"]["port"]
# Port should be the same
assert port1 == port2