fitpub/FEDERATION_TESTING_GUIDE.md
2025-12-15 21:55:17 +01:00

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)

  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:

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):

  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:

# 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:

# 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:

  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:

-- 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:

-- 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:

  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

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:

  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:
    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:
    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

-- 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

  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