Better Federation Support
This commit is contained in:
parent
15b420b87a
commit
5b687883b0
22 changed files with 2931 additions and 49 deletions
516
FEDERATION_TESTING_GUIDE.md
Normal file
516
FEDERATION_TESTING_GUIDE.md
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
# 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
|
||||
|
||||
```bash
|
||||
# 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`
|
||||
|
||||
```yaml
|
||||
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`
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
```bash
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
## Running the Instances
|
||||
|
||||
### Terminal 1: Start Instance 1
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
java -jar target/feditrack-1.0-SNAPSHOT.jar --spring.profiles.active=instance2
|
||||
```
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Test 1: User Registration
|
||||
|
||||
**Instance 1 (http://localhost:8080)**
|
||||
1. Navigate to http://localhost:8080/register
|
||||
2. Register user: `alice` / `alice@localhost1.test` / `password123`
|
||||
3. Login
|
||||
|
||||
**Instance 2 (http://localhost:8081)**
|
||||
1. Navigate to http://localhost:8081/register
|
||||
2. Register user: `bob` / `bob@localhost2.test` / `password123`
|
||||
3. Login
|
||||
|
||||
### Test 2: WebFinger Discovery
|
||||
|
||||
**From Instance 1, discover Bob on Instance 2:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/.well-known/webfinger?resource=acct:bob@localhost:8081
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
|
||||
```bash
|
||||
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):**
|
||||
|
||||
1. Login as Alice
|
||||
2. Navigate to http://localhost:8080/discover
|
||||
3. In the "Follow Remote Users" section, enter: `bob@localhost:8081`
|
||||
4. Click "Search"
|
||||
5. Verify Bob's profile appears with avatar, display name, and bio
|
||||
6. Click "Follow" button
|
||||
7. Verify notification appears: "Follow request sent to bob@localhost:8081"
|
||||
|
||||
**Verify on Instance 2 (Bob's perspective):**
|
||||
|
||||
1. Login as Bob on http://localhost:8081
|
||||
2. Check notifications - you should see: "alice@localhost:8080 followed you"
|
||||
3. Navigate to http://localhost:8081/users/bob/followers
|
||||
4. Verify alice@localhost:8080 appears in followers list
|
||||
|
||||
### Test 4: Following Relationship Check
|
||||
|
||||
**Check via API:**
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
1. Navigate to http://localhost:8081/users/bob
|
||||
2. Check "Followers" count - should be 1
|
||||
3. Click on "Followers" - Alice should be listed
|
||||
|
||||
On Instance 1:
|
||||
1. Navigate to http://localhost:8080/users/alice
|
||||
2. Check "Following" count - should be 1
|
||||
3. Click on "Following" - Bob should be listed
|
||||
|
||||
### Test 5: Activity Federation
|
||||
|
||||
**Bob uploads a workout on Instance 2:**
|
||||
|
||||
1. Login as Bob on http://localhost:8081
|
||||
2. Navigate to http://localhost:8081/upload
|
||||
3. Upload a FIT file (use test file from `src/test/resources/`)
|
||||
4. Set title: "Morning 10K Run"
|
||||
5. Set visibility: "Public"
|
||||
6. Click "Upload"
|
||||
|
||||
**Verify on Instance 1 (Alice's federated timeline):**
|
||||
|
||||
1. Login as Alice on http://localhost:8080
|
||||
2. Navigate to http://localhost:8080/timeline/federated
|
||||
3. 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:
|
||||
1. Click on Bob's "Morning 10K Run" activity title
|
||||
2. Verify it opens Bob's activity on Instance 2 (http://localhost:8081/activities/{id}) in a new tab
|
||||
3. Alternatively, click "View on Origin Server" button
|
||||
|
||||
### Test 7: Incoming Activity via ActivityPub
|
||||
|
||||
**Test with manual ActivityPub POST:**
|
||||
|
||||
```bash
|
||||
# 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:**
|
||||
|
||||
```sql
|
||||
-- On Instance 1
|
||||
SELECT * FROM remote_activities WHERE remote_actor_uri = 'http://localhost:8081/users/bob';
|
||||
```
|
||||
|
||||
### Test 8: Unfollow Workflow
|
||||
|
||||
**Alice unfollows Bob:**
|
||||
|
||||
1. On Instance 1, navigate to http://localhost:8080/users/alice/following
|
||||
2. Find Bob in the following list
|
||||
3. Click "Unfollow"
|
||||
4. Verify confirmation dialog
|
||||
5. 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:**
|
||||
|
||||
```sql
|
||||
-- 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):**
|
||||
|
||||
1. On Instance 2, login as Bob
|
||||
2. Navigate to http://localhost:8081/discover
|
||||
3. Search for: `alice@localhost:8080`
|
||||
4. Click "Follow"
|
||||
5. Verify "Follow request sent" notification
|
||||
|
||||
**Check Follow Status:**
|
||||
|
||||
```sql
|
||||
-- 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:**
|
||||
|
||||
```sql
|
||||
-- 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:
|
||||
1. Navigate to http://localhost:8081/notifications
|
||||
2. 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
|
||||
```bash
|
||||
lsof -ti:8080 | xargs kill -9
|
||||
```
|
||||
|
||||
### Database Connection Error
|
||||
|
||||
**Problem:** Connection refused to PostgreSQL
|
||||
|
||||
**Solution:** Check PostgreSQL is running
|
||||
```bash
|
||||
brew services start postgresql
|
||||
# or
|
||||
sudo systemctl start postgresql
|
||||
```
|
||||
|
||||
### WebFinger Not Working
|
||||
|
||||
**Problem:** 404 when accessing /.well-known/webfinger
|
||||
|
||||
**Solution:**
|
||||
1. Check if the controller is mapped correctly
|
||||
2. Verify Spring Security allows unauthenticated access to WebFinger endpoint
|
||||
3. Check logs for any errors
|
||||
|
||||
### Remote Activities Not Appearing
|
||||
|
||||
**Problem:** Bob's activities don't show up in Alice's federated timeline
|
||||
|
||||
**Solution:**
|
||||
1. Verify follow relationship exists and status is ACCEPTED:
|
||||
```sql
|
||||
SELECT * FROM follows WHERE follower_id = (SELECT id FROM users WHERE username = 'alice')
|
||||
AND following_actor_uri LIKE '%bob%';
|
||||
```
|
||||
2. Check InboxProcessor logs for incoming Create activities
|
||||
3. Verify RemoteActivity was created:
|
||||
```sql
|
||||
SELECT * FROM remote_activities;
|
||||
```
|
||||
4. 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 `mapImageUrl` field 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_url` is 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
|
||||
|
||||
```sql
|
||||
-- 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
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
actor_uri,
|
||||
username,
|
||||
domain,
|
||||
display_name,
|
||||
last_fetched
|
||||
FROM remote_actors
|
||||
ORDER BY last_fetched DESC;
|
||||
```
|
||||
|
||||
### Check Remote Activities
|
||||
|
||||
```sql
|
||||
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:
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
1. **Use Browser Developer Tools** to inspect network requests for ActivityPub activities
|
||||
2. **Monitor Logs** in both terminal windows to see federation events in real-time
|
||||
3. **Test Edge Cases** like following yourself, duplicate follows, following non-existent users
|
||||
4. **Test Different Visibility Levels** (PUBLIC, FOLLOWERS, PRIVATE)
|
||||
5. **Simulate Network Failures** by stopping one instance mid-federation
|
||||
6. **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
|
||||
Loading…
Add table
Add a link
Reference in a new issue