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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)

## [0.6.1]
### Added
- Test cases for most CLI commands [#18](https://github.com/jbencina/vecsync/issues/18)
### Changed
- Moved OpenAI mock classes for better unit test sharing

## [0.6.0]
### Added
- Added CLI commands for `assistants list` and `assistants clean`
- Automatic cleanup of any extra assistants in user account when initiating chat
- Added docstrings for undocumented functions
- Test case coverage for most OpenAI chat and vector store operations [#15](https://github.com/jbencina/vecsync/issues/15)

### Changed
- Updated CLI chat command to `vs chat`
- Refactored CLI into separate modules
Expand Down
150 changes: 0 additions & 150 deletions src/vecsync/cli.py

This file was deleted.

2 changes: 2 additions & 0 deletions src/vecsync/cli/entry.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# pragma: exclude file

import click

from vecsync.cli.assistants import group as assistants_group
Expand Down
1 change: 1 addition & 0 deletions src/vecsync/cli/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def clear():
"""Clear the settings file."""
settings = Settings()
settings.delete()
click.echo(colored("Settings file cleared.", "green"))


@click.command()
Expand Down
70 changes: 70 additions & 0 deletions tests/cli/test_cli_assistants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from click.testing import CliRunner

import vecsync.cli.assistants as cli


def test_list_assistants_empty(monkeypatch, mocked_client):
monkeypatch.setattr("vecsync.cli.assistants.OpenAIClient", lambda store_name: mocked_client)

runner = CliRunner()
result = runner.invoke(cli.list_assistants)
assert result.exit_code == 0

assert "No assistants found." in result.output


def test_list_assistants_non_empty(monkeypatch, mocked_client):
mocked_client.client.beta.assistants.create(name="vecsync-1")
mocked_client.client.beta.assistants.create(name="other-1")
monkeypatch.setattr("vecsync.cli.assistants.OpenAIClient", lambda store_name: mocked_client)

runner = CliRunner()
result = runner.invoke(cli.list_assistants)
assert result.exit_code == 0

assert "Assistants in your OpenAI account:" in result.output
assert "vecsync-1" in result.output
assert "other-1" not in result.output


def test_clean_assistants_empty(monkeypatch, mocked_client):
monkeypatch.setattr("vecsync.cli.assistants.OpenAIClient", lambda store_name: mocked_client)

runner = CliRunner()
result = runner.invoke(cli.clean)
assert result.exit_code == 0

assert "No deletable assistants found." in result.output


def test_clean_assistants_non_empty(monkeypatch, mocked_client):
mocked_client.client.beta.assistants.create(name="vecsync-1")
monkeypatch.setattr("vecsync.cli.assistants.OpenAIClient", lambda store_name: mocked_client)

runner = CliRunner()
result = runner.invoke(cli.clean, input="y\n")
assert result.exit_code == 0

assert "Deleting assistant vecsync-1" in result.output


def test_clean_assistants_abort(monkeypatch, mocked_client):
mocked_client.client.beta.assistants.create(name="vecsync-1")
monkeypatch.setattr("vecsync.cli.assistants.OpenAIClient", lambda store_name: mocked_client)

runner = CliRunner()
result = runner.invoke(cli.clean, input="n\n")
assert result.exit_code == 0

assert "Aborting" in result.output


def test_clean_assistants_invalid(monkeypatch, mocked_client):
mocked_client.client.beta.assistants.create(name="vecsync-1")
monkeypatch.setattr("vecsync.cli.assistants.OpenAIClient", lambda store_name: mocked_client)

runner = CliRunner()
result = runner.invoke(cli.clean, input="f\n")
assert result.exit_code == 1

assert "Please enter" in result.output
32 changes: 32 additions & 0 deletions tests/cli/test_cli_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from click.testing import CliRunner

import vecsync.cli.settings as cli
from vecsync.settings import Settings


def test_settings_show(monkeypatch, tmp_path):
# Mock the Settings class and its methods
settings_file = tmp_path / "settings.json"
monkeypatch.setattr("vecsync.cli.settings.Settings", lambda: Settings(settings_file))

runner = CliRunner()
result = runner.invoke(cli.show)
assert result.exit_code == 0
assert f"Settings file location: {settings_file}" in result.output
assert "Settings file data:" in result.output


def test_settings_delete(monkeypatch, tmp_path):
# Mock the Settings class and its methods
settings_file = tmp_path / "settings.json"
settings = Settings(settings_file)
settings["key"] = "value"

monkeypatch.setattr("vecsync.cli.settings.Settings", lambda: Settings(settings_file))

runner = CliRunner()
result = runner.invoke(cli.clear)

assert result.exit_code == 0
assert "Settings file cleared." in result.output
assert not settings_file.exists()
45 changes: 45 additions & 0 deletions tests/cli/test_cli_store.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from click.testing import CliRunner

import vecsync.cli.store as cli


def test_list_stores_empty(monkeypatch, mocked_vector_store):
monkeypatch.setattr("vecsync.cli.store.OpenAiVectorStore", lambda _: mocked_vector_store)

runner = CliRunner()
result = runner.invoke(cli.list_stores)
assert result.exit_code == 0

assert "0 Files in store 'test_store':" in result.output


def test_list_stores_non_empty(monkeypatch, mocked_vector_store, tmp_path):
filename = tmp_path / "data.pdf"
with open(filename, "w") as f:
f.write("Test data")

mocked_vector_store._upload_files({filename})
monkeypatch.setattr("vecsync.cli.store.OpenAiVectorStore", lambda _: mocked_vector_store)

runner = CliRunner()
result = runner.invoke(cli.list_stores)
assert result.exit_code == 0

assert "1 Files in store 'test_store':" in result.output
assert "data.pdf" in result.output


def test_delete_stores(monkeypatch, mocked_vector_store, tmp_path):
filename = tmp_path / "data.pdf"
with open(filename, "w") as f:
f.write("Test data")

mocked_vector_store._upload_files({filename})
monkeypatch.setattr("vecsync.cli.store.OpenAiVectorStore", lambda _: mocked_vector_store)

runner = CliRunner()
result = runner.invoke(cli.delete)
assert result.exit_code == 0

assert "Deleting 1 files from" in result.output
assert "Deleting vector store" in result.output
22 changes: 22 additions & 0 deletions tests/cli/test_cli_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from click.testing import CliRunner

import vecsync.cli.sync as cli


def test_sync_filesource(monkeypatch, tmp_path, mocked_vector_store):
filename = tmp_path / "data.pdf"

with open(filename, "w") as f:
f.write("Test data")

monkeypatch.chdir(tmp_path)
monkeypatch.setattr("vecsync.cli.sync.OpenAiVectorStore", lambda _: mocked_vector_store)

runner = CliRunner()
result = runner.invoke(cli.sync, ["--source", "file"])
assert result.exit_code == 0

assert "Syncing 1 files from local to OpenAI" in result.output
assert "Saved: 1 | Deleted: 0 | Skipped: 0" in result.output

assert len(mocked_vector_store.get_files()) == 1
Loading