12 KiB
FitPub Federation Testing Guide
This guide explains how to test the instance-to-instance federation functionality by running two FitPub instances locally.
Prerequisites
- Java 17+
- Maven 3.8+
- PostgreSQL 13+ with PostGIS extension
- Two separate PostgreSQL databases
- Two different port numbers for the applications
Setup
Step 1: Create Two PostgreSQL Databases
# Connect to PostgreSQL
psql -U postgres
# Create databases
CREATE DATABASE fitpub_instance1;
CREATE DATABASE fitpub_instance2;
# Enable PostGIS extension for both databases
\c fitpub_instance1
CREATE EXTENSION IF NOT EXISTS postgis;
\c fitpub_instance2
CREATE EXTENSION IF NOT EXISTS postgis;
\q
Step 2: Prepare Application Profiles
Create two separate application configuration files:
application-instance1.yml
server:
port: 8080
spring:
datasource:
url: jdbc:postgresql://localhost:5432/fitpub_instance1
username: postgres
password: your_password
jpa:
hibernate:
ddl-auto: validate
fitpub:
base-url: http://localhost:8080
domain: localhost:8080
logging:
level:
org.operaton.fitpub: DEBUG
application-instance2.yml
server:
port: 8081
spring:
datasource:
url: jdbc:postgresql://localhost:5432/fitpub_instance2
username: postgres
password: your_password
jpa:
hibernate:
ddl-auto: validate
fitpub:
base-url: http://localhost:8081
domain: localhost:8081
logging:
level:
org.operaton.fitpub: DEBUG
Step 3: Build the Application
mvn clean package -DskipTests
Running the Instances
Terminal 1: Start Instance 1
java -jar target/feditrack-1.0-SNAPSHOT.jar --spring.profiles.active=instance1
Wait for the application to start completely. You should see:
Started FitPubApplication in X.XXX seconds
Terminal 2: Start Instance 2
java -jar target/feditrack-1.0-SNAPSHOT.jar --spring.profiles.active=instance2
Test Scenarios
Test 1: User Registration
Instance 1 (http://localhost:8080)
- Navigate to http://localhost:8080/register
- Register user:
alice/alice@localhost1.test/password123 - Login
Instance 2 (http://localhost:8081)
- Navigate to http://localhost:8081/register
- Register user:
bob/bob@localhost2.test/password123 - Login
Test 2: WebFinger Discovery
From Instance 1, discover Bob on Instance 2:
curl http://localhost:8080/.well-known/webfinger?resource=acct:bob@localhost:8081
Expected response:
{
"subject": "acct:bob@localhost:8081",
"aliases": [
"http://localhost:8081/users/bob"
],
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "http://localhost:8081/users/bob"
}
]
}
From Instance 2, discover Alice on Instance 1:
curl http://localhost:8081/.well-known/webfinger?resource=acct:alice@localhost:8080
Test 3: Remote User Discovery via UI
On Instance 1 (Alice following Bob):
- Login as Alice
- Navigate to http://localhost:8080/discover
- In the "Follow Remote Users" section, enter:
bob@localhost:8081 - Click "Search"
- Verify Bob's profile appears with avatar, display name, and bio
- Click "Follow" button
- Verify notification appears: "Follow request sent to bob@localhost:8081"
Verify on Instance 2 (Bob's perspective):
- Login as Bob on http://localhost:8081
- Check notifications - you should see: "alice@localhost:8080 followed you"
- Navigate to http://localhost:8081/users/bob/followers
- Verify alice@localhost:8080 appears in followers list
Test 4: Following Relationship Check
Check via API:
# From Instance 2, check Bob's followers
curl http://localhost:8081/api/users/bob/followers | jq
# Expected: Alice should be in the list
Check via UI:
On Instance 2:
- Navigate to http://localhost:8081/users/bob
- Check "Followers" count - should be 1
- Click on "Followers" - Alice should be listed
On Instance 1:
- Navigate to http://localhost:8080/users/alice
- Check "Following" count - should be 1
- Click on "Following" - Bob should be listed
Test 5: Activity Federation
Bob uploads a workout on Instance 2:
- Login as Bob on http://localhost:8081
- Navigate to http://localhost:8081/upload
- Upload a FIT file (use test file from
src/test/resources/) - Set title: "Morning 10K Run"
- Set visibility: "Public"
- Click "Upload"
Verify on Instance 1 (Alice's federated timeline):
- Login as Alice on http://localhost:8080
- Navigate to http://localhost:8080/timeline/federated
- Verify Bob's "Morning 10K Run" activity appears with:
- Federation badge: "🌐 Remote"
- Bob's avatar and @bob@localhost:8081
- Map preview (if map image URL is available)
- Metrics (distance, duration, pace, elevation)
- Link to view on origin server
Test 6: Remote Activity Details
Click on Remote Activity:
From Alice's federated timeline:
- Click on Bob's "Morning 10K Run" activity title
- Verify it opens Bob's activity on Instance 2 (http://localhost:8081/activities/{id}) in a new tab
- Alternatively, click "View on Origin Server" button
Test 7: Incoming Activity via ActivityPub
Test with manual ActivityPub POST:
# Create a test activity
cat > test-activity.json <<EOF
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Create",
"id": "http://localhost:8081/activities/create/test-123",
"actor": "http://localhost:8081/users/bob",
"published": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["http://localhost:8081/users/bob/followers"],
"object": {
"type": "Note",
"id": "http://localhost:8081/workouts/test-456",
"attributedTo": "http://localhost:8081/users/bob",
"name": "Test Workout via ActivityPub",
"content": "Testing direct ActivityPub activity delivery",
"published": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"workoutData": {
"activityType": "RUN",
"distance": 5000,
"duration": "PT25M30S",
"elevationGain": 50
},
"attachment": [
{
"type": "Document",
"mediaType": "image/png",
"name": "Map",
"url": "http://localhost:8081/activities/test-456/map.png"
}
]
}
}
EOF
# Post to Alice's inbox on Instance 1
curl -X POST http://localhost:8080/users/alice/inbox \
-H "Content-Type: application/activity+json" \
-d @test-activity.json
# Expected response: 202 Accepted
Verify in database:
-- On Instance 1
SELECT * FROM remote_activities WHERE remote_actor_uri = 'http://localhost:8081/users/bob';
Test 8: Unfollow Workflow
Alice unfollows Bob:
- On Instance 1, navigate to http://localhost:8080/users/alice/following
- Find Bob in the following list
- Click "Unfollow"
- Verify confirmation dialog
- Confirm unfollow
Verify Undo Activity:
Check Instance 2 logs for incoming Undo activity:
Processing Undo activity for user bob
Deleted follow from actor: http://localhost:8080/users/alice
Check Database:
-- On Instance 2
SELECT * FROM follows WHERE remote_actor_uri = 'http://localhost:8080/users/alice'
AND following_actor_uri = 'http://localhost:8081/users/bob';
-- Should return 0 rows
Test 9: Accept Activity Flow
Bob follows Alice (reverse direction):
- On Instance 2, login as Bob
- Navigate to http://localhost:8081/discover
- Search for:
alice@localhost:8080 - Click "Follow"
- Verify "Follow request sent" notification
Check Follow Status:
-- On Instance 2
SELECT status FROM follows WHERE follower_id = (SELECT id FROM users WHERE username = 'bob')
AND following_actor_uri = 'http://localhost:8080/users/alice';
-- Should return 'PENDING'
Verify Accept on Instance 1:
Check Instance 1 logs for outgoing Accept activity:
Sending Accept activity to http://localhost:8081/users/bob/inbox
Accept activity sent successfully
Check Updated Status:
-- On Instance 2
SELECT status FROM follows WHERE follower_id = (SELECT id FROM users WHERE username = 'bob')
AND following_actor_uri = 'http://localhost:8080/users/alice';
-- Should return 'ACCEPTED'
Check Notification:
On Instance 2:
- Navigate to http://localhost:8081/notifications
- Verify notification: "alice@localhost:8080 accepted your follow request"
Troubleshooting
Instance Won't Start
Problem: Port already in use
Port 8080 is already in use
Solution: Kill the process using the port or use a different port
lsof -ti:8080 | xargs kill -9
Database Connection Error
Problem: Connection refused to PostgreSQL
Solution: Check PostgreSQL is running
brew services start postgresql
# or
sudo systemctl start postgresql
WebFinger Not Working
Problem: 404 when accessing /.well-known/webfinger
Solution:
- Check if the controller is mapped correctly
- Verify Spring Security allows unauthenticated access to WebFinger endpoint
- Check logs for any errors
Remote Activities Not Appearing
Problem: Bob's activities don't show up in Alice's federated timeline
Solution:
- Verify follow relationship exists and status is ACCEPTED:
SELECT * FROM follows WHERE follower_id = (SELECT id FROM users WHERE username = 'alice') AND following_actor_uri LIKE '%bob%'; - Check InboxProcessor logs for incoming Create activities
- Verify RemoteActivity was created:
SELECT * FROM remote_activities; - Check TimelineService is fetching both local and remote activities
Map Preview Not Loading
Problem: Remote activity map shows "Map not available"
Solution:
- Remote activities use
mapImageUrlfield which must be set when creating the activity - For testing, you may need to implement map image generation on the origin server
- Check if the URL in
map_image_urlis accessible
Validation Checklist
- Both instances start successfully on different ports
- WebFinger discovery works in both directions
- Remote user discovery UI works
- Follow request is sent and creates PENDING follow
- Accept activity is received and updates status to ACCEPTED
- Follower/following lists show both local and remote users
- Remote activities appear in federated timeline
- Remote activities show federation badge
- Map preview loads from remote server
- "View on Origin Server" opens correct URL
- Unfollow sends Undo activity and removes follow
- Notifications are created for follow/accept events
- No errors in console logs
Database Inspection Queries
Check All Follows
-- On any instance
SELECT
f.id,
f.follower_id,
u.username as follower_username,
f.following_actor_uri,
f.remote_actor_uri,
f.status,
f.created_at
FROM follows f
LEFT JOIN users u ON f.follower_id = u.id
ORDER BY f.created_at DESC;
Check Remote Actors
SELECT
actor_uri,
username,
domain,
display_name,
last_fetched
FROM remote_actors
ORDER BY last_fetched DESC;
Check Remote Activities
SELECT
id,
activity_uri,
remote_actor_uri,
activity_type,
title,
total_distance,
total_duration_seconds,
published_at,
visibility,
map_image_url
FROM remote_activities
ORDER BY published_at DESC;
Clean Up
To reset the test environment:
# Stop both instances (Ctrl+C in both terminals)
# Drop and recreate databases
psql -U postgres <<EOF
DROP DATABASE fitpub_instance1;
DROP DATABASE fitpub_instance2;
CREATE DATABASE fitpub_instance1;
CREATE DATABASE fitpub_instance2;
\c fitpub_instance1
CREATE EXTENSION IF NOT EXISTS postgis;
\c fitpub_instance2
CREATE EXTENSION IF NOT EXISTS postgis;
EOF
Additional Testing Tips
- Use Browser Developer Tools to inspect network requests for ActivityPub activities
- Monitor Logs in both terminal windows to see federation events in real-time
- Test Edge Cases like following yourself, duplicate follows, following non-existent users
- Test Different Visibility Levels (PUBLIC, FOLLOWERS, PRIVATE)
- Simulate Network Failures by stopping one instance mid-federation
- Test Concurrent Operations (both users following each other simultaneously)
Success Criteria
✅ All checklist items pass ✅ No errors in logs ✅ Activities federate within 1-2 seconds ✅ UI updates reflect federation state correctly ✅ Database state is consistent across both instances