Skip to content

Commit

Permalink
Header update
Browse files Browse the repository at this point in the history
  • Loading branch information
jahwag committed Jul 23, 2024
1 parent f61fefc commit df041dd
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 131 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "claudesync"
version = "0.3.5"
version = "0.3.6"
authors = [
{name = "Jahziah Wagner", email = "[email protected]"},
]
Expand Down
13 changes: 13 additions & 0 deletions src/claudesync/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,16 @@ def get_headers(self):
dict: The current headers configuration.
"""
return self.config.get("headers", {})

def update_cookies(self, new_cookies):
"""
Updates the cookies configuration with new values.
Args:
new_cookies (dict): A dictionary containing the new cookie key-value pairs to update or add.
This method updates the existing cookies with the new values provided, adds any new cookies,
and then saves the updated configuration to the file.
"""
self.config.setdefault("cookies", {}).update(new_cookies)
self._save_config()
53 changes: 31 additions & 22 deletions src/claudesync/providers/claude_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,30 +61,36 @@ def _configure_logging(self):
) # Set logger instance to the specified log level

def _make_request(self, method, endpoint, **kwargs):
"""
Sends a request to the specified endpoint using the given HTTP method.
This method constructs a request to the Claude AI API, appending the specified endpoint to the base URL.
It sets up common headers and cookies for the request, including a session key for authentication.
Additional headers can be provided through `kwargs`. The method logs the request and response details
and handles common HTTP errors and JSON parsing errors.
Args:
method (str): The HTTP method to use for the request (e.g., 'GET', 'POST').
endpoint (str): The API endpoint to which the request is sent.
**kwargs: Arbitrary keyword arguments. Can include 'headers' to add or override default headers,
and any other request parameters.
Returns:
dict or None: The parsed JSON response from the API if the response contains content; otherwise, None.
Raises:
ProviderError: If the request fails, if the response status code indicates an error,
or if the response cannot be parsed as JSON.
"""
url = f"{self.BASE_URL}{endpoint}"
headers = self.config.get_headers()
cookies = {"sessionKey": self.session_key}
cookies = self.config.get("cookies", {})

# Update headers
headers.update(
{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
"Origin": "https://claude.ai",
"Referer": "https://claude.ai/projects",
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Accept-Language": "en-US,en;q=0.5",
"anthropic-client-sha": "unknown",
"anthropic-client-version": "unknown",
"Connection": "keep-alive",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
}
)

# Merge cookies
cookies.update(
{
"sessionKey": self.session_key,
"CH-prefers-color-scheme": "dark",
"anthropic-consent-preferences": '{"analytics":true,"marketing":true}',
}
)

if "headers" in kwargs:
headers.update(kwargs.pop("headers"))
Expand All @@ -106,6 +112,9 @@ def _make_request(self, method, endpoint, **kwargs):
f"Response content: {response.text[:1000]}..."
) # Log first 1000 characters of response

# Update cookies with any new values from the response
self.config.update_cookies(response.cookies.get_dict())

response.raise_for_status()

if not response.content:
Expand Down
219 changes: 111 additions & 108 deletions tests/providers/test_claude_ai.py
Original file line number Diff line number Diff line change
@@ -1,141 +1,144 @@
import unittest
from unittest.mock import patch, MagicMock
import json
import requests
from claudesync.providers.claude_ai import ClaudeAIProvider

from claudesync.exceptions import ProviderError
from claudesync.config_manager import ConfigManager

class TestClaudeAIProvider(unittest.TestCase):

def setUp(self):
self.provider = ClaudeAIProvider()
self.provider = ClaudeAIProvider("test_session_key")
# Mock ConfigManager
self.mock_config = MagicMock(spec=ConfigManager)
self.mock_config.get_headers.return_value = {}
self.mock_config.get.return_value = {}
self.provider.config = self.mock_config

@patch('claudesync.providers.claude_ai.requests.request')
def test_make_request_success(self, mock_request):
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"key": "value"}
mock_request.return_value = mock_response

result = self.provider._make_request("GET", "/test")

self.assertEqual(result, {"key": "value"})
mock_request.assert_called_once()

@patch("claudesync.providers.claude_ai.click.prompt")
@patch('claudesync.providers.claude_ai.requests.request')
def test_make_request_failure(self, mock_request):
mock_request.side_effect = requests.RequestException("Test error")

with self.assertRaises(ProviderError):
self.provider._make_request("GET", "/test")

@patch('claudesync.providers.claude_ai.click.prompt')
def test_login(self, mock_prompt):
mock_prompt.return_value = "test_session_key"
session_key = self.provider.login()
self.assertEqual(session_key, "test_session_key")
mock_prompt.return_value = "new_session_key"

@patch("claudesync.providers.claude_ai.requests.request")
def test_get_organizations(self, mock_request):
mock_response = MagicMock()
mock_response.json.return_value = [
{
"uuid": "org1",
"name": "Organization 1",
"settings": {},
"capabilities": [],
"rate_limit_tier": "",
"billing_type": "",
"created_at": "",
"updated_at": "",
},
{
"uuid": "org2",
"name": "Organization 2",
"settings": {},
"capabilities": [],
"rate_limit_tier": "",
"billing_type": "",
"created_at": "",
"updated_at": "",
},
result = self.provider.login()

self.assertEqual(result, "new_session_key")
self.assertEqual(self.provider.session_key, "new_session_key")

@patch('claudesync.providers.claude_ai.ClaudeAIProvider._make_request')
def test_get_organizations(self, mock_make_request):
mock_make_request.return_value = [
{"uuid": "org1", "name": "Org 1"},
{"uuid": "org2", "name": "Org 2"}
]
mock_request.return_value = mock_response

organizations = self.provider.get_organizations()
self.assertEqual(len(organizations), 2)
self.assertEqual(organizations[0]["id"], "org1")
self.assertEqual(organizations[0]["name"], "Organization 1")
self.assertEqual(organizations[1]["id"], "org2")
self.assertEqual(organizations[1]["name"], "Organization 2")
result = self.provider.get_organizations()

@patch("claudesync.providers.claude_ai.requests.request")
def test_get_projects(self, mock_request):
mock_response = MagicMock()
mock_response.json.return_value = [
expected = [
{"id": "org1", "name": "Org 1"},
{"id": "org2", "name": "Org 2"}
]
self.assertEqual(result, expected)

@patch('claudesync.providers.claude_ai.ClaudeAIProvider._make_request')
def test_get_projects(self, mock_make_request):
mock_make_request.return_value = [
{"uuid": "proj1", "name": "Project 1", "archived_at": None},
{"uuid": "proj2", "name": "Project 2", "archived_at": "2023-01-01"},
{"uuid": "proj2", "name": "Project 2", "archived_at": "2023-01-01"}
]
mock_request.return_value = mock_response

projects = self.provider.get_projects("org1", include_archived=True)
self.assertEqual(len(projects), 2)
self.assertEqual(projects[0]["id"], "proj1")
self.assertEqual(projects[0]["name"], "Project 1")
self.assertIsNone(projects[0]["archived_at"])
self.assertEqual(projects[1]["id"], "proj2")
self.assertEqual(projects[1]["name"], "Project 2")
self.assertEqual(projects[1]["archived_at"], "2023-01-01")

@patch("claudesync.providers.claude_ai.requests.request")
def test_list_files(self, mock_request):
mock_response = MagicMock()
mock_response.json.return_value = [
{
"uuid": "file1",
"file_name": "test1.txt",
"content": "Hello",
"created_at": "2023-01-01",
},
{
"uuid": "file2",
"file_name": "test2.txt",
"content": "World",
"created_at": "2023-01-02",
},
result = self.provider.get_projects("org1", include_archived=True)

expected = [
{"id": "proj1", "name": "Project 1", "archived_at": None},
{"id": "proj2", "name": "Project 2", "archived_at": "2023-01-01"}
]
mock_request.return_value = mock_response
self.assertEqual(result, expected)

files = self.provider.list_files("org1", "proj1")
self.assertEqual(len(files), 2)
self.assertEqual(files[0]["uuid"], "file1")
self.assertEqual(files[0]["file_name"], "test1.txt")
self.assertEqual(files[0]["content"], "Hello")
self.assertEqual(files[0]["created_at"], "2023-01-01")
@patch('claudesync.providers.claude_ai.ClaudeAIProvider._make_request')
def test_list_files(self, mock_make_request):
mock_make_request.return_value = [
{"uuid": "file1", "file_name": "test1.txt", "content": "content1", "created_at": "2023-01-01"},
{"uuid": "file2", "file_name": "test2.txt", "content": "content2", "created_at": "2023-01-02"}
]

@patch("claudesync.providers.claude_ai.requests.request")
def test_upload_file(self, mock_request):
mock_response = MagicMock()
mock_response.json.return_value = {"uuid": "new_file"}
mock_request.return_value = mock_response
result = self.provider.list_files("org1", "proj1")

expected = [
{"uuid": "file1", "file_name": "test1.txt", "content": "content1", "created_at": "2023-01-01"},
{"uuid": "file2", "file_name": "test2.txt", "content": "content2", "created_at": "2023-01-02"}
]
self.assertEqual(result, expected)

result = self.provider.upload_file(
"org1", "proj1", "new_file.txt", "New content"
@patch('claudesync.providers.claude_ai.ClaudeAIProvider._make_request')
def test_upload_file(self, mock_make_request):
mock_make_request.return_value = {"uuid": "new_file", "file_name": "test.txt"}

result = self.provider.upload_file("org1", "proj1", "test.txt", "content")

self.assertEqual(result, {"uuid": "new_file", "file_name": "test.txt"})
mock_make_request.assert_called_once_with(
"POST",
"/organizations/org1/projects/proj1/docs",
json={"file_name": "test.txt", "content": "content"}
)
self.assertEqual(result["uuid"], "new_file")

@patch("claudesync.providers.claude_ai.requests.request")
def test_delete_file(self, mock_request):
mock_response = MagicMock()
mock_response.status_code = 204
mock_request.return_value = mock_response
@patch('claudesync.providers.claude_ai.ClaudeAIProvider._make_request')
def test_delete_file(self, mock_make_request):
mock_make_request.return_value = {"status": "deleted"}

self.provider.delete_file("org1", "proj1", "file1")
mock_request.assert_called_once_with(
result = self.provider.delete_file("org1", "proj1", "file1")

self.assertEqual(result, {"status": "deleted"})
mock_make_request.assert_called_once_with(
"DELETE",
f"{self.provider.BASE_URL}/organizations/org1/projects/proj1/docs/file1",
headers=unittest.mock.ANY,
cookies=unittest.mock.ANY,
"/organizations/org1/projects/proj1/docs/file1"
)

@patch("claudesync.providers.claude_ai.requests.request")
def test_archive_project(self, mock_request):
mock_response = MagicMock()
mock_response.json.return_value = {"is_archived": True}
mock_request.return_value = mock_response
@patch('claudesync.providers.claude_ai.ClaudeAIProvider._make_request')
def test_archive_project(self, mock_make_request):
mock_make_request.return_value = {"uuid": "proj1", "is_archived": True}

result = self.provider.archive_project("org1", "proj1")
self.assertTrue(result["is_archived"])

@patch("claudesync.providers.claude_ai.requests.request")
def test_create_project(self, mock_request):
mock_response = MagicMock()
mock_response.json.return_value = {"uuid": "new_proj", "name": "New Project"}
mock_request.return_value = mock_response
self.assertEqual(result, {"uuid": "proj1", "is_archived": True})
mock_make_request.assert_called_once_with(
"PUT",
"/organizations/org1/projects/proj1",
json={"is_archived": True}
)

@patch('claudesync.providers.claude_ai.ClaudeAIProvider._make_request')
def test_create_project(self, mock_make_request):
mock_make_request.return_value = {"uuid": "new_proj", "name": "New Project"}

result = self.provider.create_project("org1", "New Project", "Description")
self.assertEqual(result["uuid"], "new_proj")
self.assertEqual(result["name"], "New Project")

self.assertEqual(result, {"uuid": "new_proj", "name": "New Project"})
mock_make_request.assert_called_once_with(
"POST",
"/organizations/org1/projects",
json={"name": "New Project", "description": "Description", "is_private": True}
)

if __name__ == "__main__":
unittest.main()
if __name__ == '__main__':
unittest.main()

0 comments on commit df041dd

Please sign in to comment.