Skip to content

Commit ac33919

Browse files
rokroskarCline
andcommitted
fix: include activitypub module in all project packages
This commit fixes the issue where the ActivityPub module was not being included in the deployed application. The module was correctly listed in the root pyproject.toml file, but was missing from the project-specific pyproject.toml files that are used to build the actual deployable packages. - Added activitypub module to projects/renku_data_service/pyproject.toml - Added activitypub module to projects/background_jobs/pyproject.toml - Added activitypub module to projects/secrets_storage/pyproject.toml - Added tests for WebFinger functionality and serialization to prevent regressions Co-authored-by: Cline <[email protected]>
1 parent ba309d5 commit ac33919

File tree

5 files changed

+301
-221
lines changed

5 files changed

+301
-221
lines changed

projects/background_jobs/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ packages = [
4242
{ include = "renku_data_services/notebooks", from = "../../components" },
4343
{ include = "renku_data_services/solr", from = "../../components" },
4444
{ include = "renku_data_services/search", from = "../../components" },
45+
{ include = "renku_data_services/activitypub", from = "../../components" },
4546
]
4647

4748
[tool.poetry.dependencies]

projects/renku_data_service/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ packages = [
4040
{ include = "renku_data_services/migrations", from = "../../components" },
4141
{ include = "renku_data_services/solr", from = "../../components" },
4242
{ include = "renku_data_services/search", from = "../../components" },
43+
{ include = "renku_data_services/activitypub", from = "../../components" },
4344
]
4445

4546
[tool.poetry.dependencies]

projects/secrets_storage/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ packages = [
4040
{ include = "renku_data_services/notebooks", from = "../../components" },
4141
{ include = "renku_data_services/solr", from = "../../components" },
4242
{ include = "renku_data_services/search", from = "../../components" },
43+
{ include = "renku_data_services/activitypub", from = "../../components" },
4344
]
4445

4546
[tool.poetry.dependencies]
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
"""Tests for ActivityPub serialization functionality."""
2+
3+
from datetime import UTC, datetime
4+
from dataclasses import dataclass
5+
from typing import Any, Dict, List, Optional
6+
7+
import pytest
8+
from ulid import ULID
9+
10+
from renku_data_services.activitypub.core import ActivityPubService
11+
from renku_data_services.activitypub.db import ActivityPubRepository
12+
from renku_data_services.activitypub import models
13+
14+
15+
@dataclass
16+
class TestDataclass:
17+
"""Test dataclass for serialization tests."""
18+
19+
name: str
20+
value: int
21+
nested: Optional[Dict[str, Any]] = None
22+
context: Optional[str] = None
23+
24+
25+
@pytest.fixture
26+
def service(mock_project_repo, mock_config):
27+
"""Create an ActivityPub service for testing."""
28+
activitypub_repo = ActivityPubRepository(
29+
session_maker=lambda: None,
30+
project_repo=mock_project_repo,
31+
config=mock_config,
32+
)
33+
return ActivityPubService(
34+
activitypub_repo=activitypub_repo,
35+
project_repo=mock_project_repo,
36+
config=mock_config,
37+
)
38+
39+
40+
def test_to_dict_handles_basic_types(service):
41+
"""Test that _to_dict correctly handles basic Python types."""
42+
# Test with a string
43+
assert service._to_dict("test") == "test"
44+
45+
# Test with an integer
46+
assert service._to_dict(42) == 42
47+
48+
# Test with a float
49+
assert service._to_dict(3.14) == 3.14
50+
51+
# Test with a boolean
52+
assert service._to_dict(True) is True
53+
assert service._to_dict(False) is False
54+
55+
# Test with None
56+
assert service._to_dict(None) is None
57+
58+
59+
def test_to_dict_handles_complex_types(service):
60+
"""Test that _to_dict correctly handles complex Python types."""
61+
# Test with a list
62+
assert service._to_dict([1, 2, 3]) == [1, 2, 3]
63+
64+
# Test with a dictionary
65+
assert service._to_dict({"a": 1, "b": 2}) == {"a": 1, "b": 2}
66+
67+
# Test with a nested structure
68+
complex_obj = {
69+
"name": "test",
70+
"values": [1, 2, 3],
71+
"nested": {
72+
"a": True,
73+
"b": None,
74+
}
75+
}
76+
assert service._to_dict(complex_obj) == complex_obj
77+
78+
79+
def test_to_dict_handles_datetime(service):
80+
"""Test that _to_dict correctly handles datetime objects."""
81+
# Create a datetime object
82+
dt = datetime(2025, 3, 20, 12, 34, 56, tzinfo=UTC)
83+
84+
# Convert to dict
85+
result = service._to_dict(dt)
86+
87+
# Verify the result is an ISO-formatted string
88+
assert isinstance(result, str)
89+
assert result == "2025-03-20T12:34:56+00:00"
90+
91+
92+
def test_to_dict_handles_ulid(service):
93+
"""Test that _to_dict correctly handles ULID objects."""
94+
# Create a ULID object
95+
ulid = ULID()
96+
97+
# Convert to dict
98+
result = service._to_dict(ulid)
99+
100+
# Verify the result is a string representation of the ULID
101+
assert isinstance(result, str)
102+
assert result == str(ulid)
103+
104+
105+
def test_to_dict_handles_dataclasses(service):
106+
"""Test that _to_dict correctly handles dataclass objects."""
107+
# Create a dataclass instance
108+
obj = TestDataclass(
109+
name="test",
110+
value=42,
111+
nested={"a": 1, "b": 2},
112+
)
113+
114+
# Convert to dict
115+
result = service._to_dict(obj)
116+
117+
# Verify the result
118+
assert isinstance(result, dict)
119+
assert result["name"] == "test"
120+
assert result["value"] == 42
121+
assert result["nested"] == {"a": 1, "b": 2}
122+
assert "context" not in result # None values should be skipped
123+
124+
125+
def test_to_dict_handles_context_field(service):
126+
"""Test that _to_dict correctly handles the special 'context' field in dataclasses."""
127+
# Create a dataclass instance with a context field
128+
obj = TestDataclass(
129+
name="test",
130+
value=42,
131+
context="https://www.w3.org/ns/activitystreams",
132+
)
133+
134+
# Convert to dict
135+
result = service._to_dict(obj)
136+
137+
# Verify the result
138+
assert isinstance(result, dict)
139+
assert result["name"] == "test"
140+
assert result["value"] == 42
141+
assert "@context" in result # context should be converted to @context
142+
assert result["@context"] == "https://www.w3.org/ns/activitystreams"
143+
144+
145+
def test_to_dict_handles_activity_objects(service):
146+
"""Test that _to_dict correctly handles Activity objects."""
147+
# Create an Activity object
148+
activity = models.Activity(
149+
id="https://example.com/activities/1",
150+
type=models.ActivityType.ACCEPT,
151+
actor="https://example.com/users/1",
152+
object={
153+
"type": models.ActivityType.FOLLOW,
154+
"actor": "https://mastodon.social/users/test",
155+
"object": "https://example.com/projects/1",
156+
},
157+
to=["https://mastodon.social/users/test"],
158+
published=datetime.now(UTC).isoformat(),
159+
)
160+
161+
# Convert to dict
162+
result = service._to_dict(activity)
163+
164+
# Verify the result
165+
assert isinstance(result, dict)
166+
assert result["id"] == activity.id
167+
assert result["type"] == activity.type
168+
assert result["actor"] == activity.actor
169+
assert isinstance(result["object"], dict)
170+
assert result["object"]["type"] == models.ActivityType.FOLLOW
171+
assert isinstance(result["to"], list)
172+
assert result["to"][0] == "https://mastodon.social/users/test"
173+
assert isinstance(result["published"], str)
174+
175+
176+
def test_to_dict_handles_unknown_types(service):
177+
"""Test that _to_dict correctly handles unknown types by converting them to strings."""
178+
# Create a custom class
179+
class CustomClass:
180+
def __str__(self):
181+
return "CustomClass"
182+
183+
# Create an instance
184+
obj = CustomClass()
185+
186+
# Convert to dict
187+
result = service._to_dict(obj)
188+
189+
# Verify the result is a string
190+
assert isinstance(result, str)
191+
assert result == "CustomClass"

0 commit comments

Comments
 (0)