"""Tests for CLI cloud promo messaging."""
from io import StringIO
from rich.console import Console
from typer.testing import CliRunner
from basic_memory.cli.app import app
import basic_memory
from basic_memory.cli.promo import (
maybe_show_cloud_promo,
maybe_show_init_line,
)
from basic_memory.config import ConfigManager
def _capture_console() -> tuple[Console, StringIO]:
"""Create a Console that writes to an in-memory buffer."""
buf = StringIO()
return Console(file=buf, force_terminal=True), buf
# --- maybe_show_init_line tests ---
def test_init_line_shown_on_first_run():
console, buf = _capture_console()
maybe_show_init_line(
"status",
config_manager=ConfigManager(),
is_interactive=True,
console=console,
)
output = buf.getvalue()
assert "Basic Memory initialized" in output
assert "✓" in output
def test_init_line_not_shown_when_already_shown():
config_manager = ConfigManager()
config = config_manager.load_config()
config.cloud_promo_first_run_shown = True
config_manager.save_config(config)
console, buf = _capture_console()
maybe_show_init_line(
"status",
config_manager=config_manager,
is_interactive=True,
console=console,
)
assert buf.getvalue() == ""
def test_init_line_not_shown_for_mcp():
console, buf = _capture_console()
maybe_show_init_line(
"mcp",
config_manager=ConfigManager(),
is_interactive=True,
console=console,
)
assert buf.getvalue() == ""
def test_init_line_not_shown_when_env_disables_promos(monkeypatch):
monkeypatch.setenv("BASIC_MEMORY_NO_PROMOS", "1")
console, buf = _capture_console()
maybe_show_init_line(
"status",
config_manager=ConfigManager(),
is_interactive=True,
console=console,
)
assert buf.getvalue() == ""
def test_init_line_not_shown_when_not_interactive():
console, buf = _capture_console()
maybe_show_init_line(
"status",
config_manager=ConfigManager(),
is_interactive=False,
console=console,
)
assert buf.getvalue() == ""
# --- maybe_show_cloud_promo tests ---
def test_first_run_shows_cloud_panel_and_persists_flags():
console, buf = _capture_console()
maybe_show_cloud_promo(
"status",
config_manager=ConfigManager(),
is_interactive=True,
console=console,
)
output = buf.getvalue()
# Benefit-led copy
assert "Your knowledge, everywhere" in output
assert "Stop losing context" in output
assert "Basic Memory Cloud syncs your memory" in output
assert "BMFOSS" in output
assert "bm cloud login" in output
# Rich Panel title
assert "Basic Memory Cloud" in output
# Footer hints below the panel
assert "basicmemory.com" in output
assert "bm cloud promo --off" in output
config = ConfigManager().load_config()
assert config.cloud_promo_first_run_shown is True
assert config.cloud_promo_last_version_shown == basic_memory.__version__
def test_version_notice_shows_when_promo_version_changes():
config_manager = ConfigManager()
config = config_manager.load_config()
config.cloud_promo_first_run_shown = True
config.cloud_promo_last_version_shown = "2025-01-01"
config_manager.save_config(config)
console, buf = _capture_console()
maybe_show_cloud_promo(
"status",
config_manager=config_manager,
is_interactive=True,
console=console,
)
output = buf.getvalue()
# Same benefit-led copy for both first-run and version-bump
assert "Your knowledge, everywhere" in output
def test_no_message_when_already_shown_for_current_version():
config_manager = ConfigManager()
config = config_manager.load_config()
config.cloud_promo_first_run_shown = True
config.cloud_promo_last_version_shown = basic_memory.__version__
config_manager.save_config(config)
console, buf = _capture_console()
maybe_show_cloud_promo(
"status",
config_manager=config_manager,
is_interactive=True,
console=console,
)
assert buf.getvalue() == ""
def test_no_message_when_cloud_access_is_configured():
config_manager = ConfigManager()
config = config_manager.load_config()
config.cloud_api_key = "bmc_test_key_123"
config_manager.save_config(config)
console, buf = _capture_console()
maybe_show_cloud_promo(
"status",
config_manager=config_manager,
is_interactive=True,
console=console,
)
assert buf.getvalue() == ""
def test_no_message_when_user_opted_out():
config_manager = ConfigManager()
config = config_manager.load_config()
config.cloud_promo_opt_out = True
config_manager.save_config(config)
console, buf = _capture_console()
maybe_show_cloud_promo(
"status",
config_manager=config_manager,
is_interactive=True,
console=console,
)
assert buf.getvalue() == ""
def test_no_message_for_mcp_subcommand():
console, buf = _capture_console()
maybe_show_cloud_promo(
"mcp",
config_manager=ConfigManager(),
is_interactive=True,
console=console,
)
assert buf.getvalue() == ""
def test_no_message_when_env_disables_promos(monkeypatch):
monkeypatch.setenv("BASIC_MEMORY_NO_PROMOS", "1")
console, buf = _capture_console()
maybe_show_cloud_promo(
"status",
config_manager=ConfigManager(),
is_interactive=True,
console=console,
)
assert buf.getvalue() == ""
def test_no_message_when_not_interactive():
console, buf = _capture_console()
maybe_show_cloud_promo(
"status",
config_manager=ConfigManager(),
is_interactive=False,
console=console,
)
assert buf.getvalue() == ""
def test_cloud_promo_command_off_sets_opt_out(monkeypatch):
runner = CliRunner()
instances: list[object] = []
class _StubConfig:
cloud_promo_opt_out = False
class _StubConfigManager:
def __init__(self):
self._config = _StubConfig()
self.saved_config = None
instances.append(self)
def load_config(self):
return self._config
def save_config(self, config):
self.saved_config = config
monkeypatch.setattr(
"basic_memory.cli.commands.cloud.core_commands.ConfigManager",
_StubConfigManager,
)
result = runner.invoke(app, ["cloud", "promo", "--off"])
assert result.exit_code == 0
assert "Cloud promo messages disabled" in result.stdout
assert len(instances) == 1
assert instances[0].saved_config.cloud_promo_opt_out is True
def test_cloud_promo_command_on_clears_opt_out(monkeypatch):
runner = CliRunner()
instances: list[object] = []
class _StubConfig:
cloud_promo_opt_out = True
class _StubConfigManager:
def __init__(self):
self._config = _StubConfig()
self.saved_config = None
instances.append(self)
def load_config(self):
return self._config
def save_config(self, config):
self.saved_config = config
monkeypatch.setattr(
"basic_memory.cli.commands.cloud.core_commands.ConfigManager",
_StubConfigManager,
)
result = runner.invoke(app, ["cloud", "promo", "--on"])
assert result.exit_code == 0
assert "Cloud promo messages enabled" in result.stdout
assert len(instances) == 1
assert instances[0].saved_config.cloud_promo_opt_out is False