fitpub/FEDERATION_TESTING_GUIDE.md
2026-01-01 23:48:05 +01:00

696 lines
18 KiB
Markdown

# FitPub Federation Testing Guide
This guide explains how to test the instance-to-instance federation functionality by running two FitPub instances locally.
## Docker Compose Setup (Recommended)
The easiest way to test federation is using Docker Compose, which automatically sets up two complete FitPub instances with separate databases and proper networking.
### Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Docker Network (fitpub-federation) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Instance 1 │ │ Instance 2 │ │
│ │ (instance1.local) │◄─────►│ (instance2.local) │ │
│ │ Port: 8080 │ │ Port: 8081 │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ PostgreSQL 1 │ │ PostgreSQL 2 │ │
│ │ (postgres1) │ │ (postgres2) │ │
│ │ Port: 5432 │ │ Port: 5433 │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
▲ ▲
│ │
localhost:8080 localhost:8081
```
### Quick Start
1. **Start both instances**:
```bash
docker-compose -f docker-compose.federation-test.yml up -d
```
2. **Check status**:
```bash
docker-compose -f docker-compose.federation-test.yml ps
```
3. **Access the instances**:
- Instance 1: http://localhost:8080
- Instance 2: http://localhost:8081
4. **Follow the [Test Scenarios](#test-scenarios) below** to verify federation functionality
5. **View logs** (in separate terminals):
```bash
# Instance 1 logs
docker-compose -f docker-compose.federation-test.yml logs -f fitpub1
# Instance 2 logs
docker-compose -f docker-compose.federation-test.yml logs -f fitpub2
```
6. **Stop and clean up**:
```bash
# Stop containers
docker-compose -f docker-compose.federation-test.yml down
# Stop and remove volumes (complete cleanup)
docker-compose -f docker-compose.federation-test.yml down -v
```
### Service Overview
The Docker Compose setup includes:
- **postgres1**: PostgreSQL 16 with PostGIS 3.4 for Instance 1
- Database: `fitpub1`
- Port: 5432 (internal), 5434 (on host)
- **postgres2**: PostgreSQL 16 with PostGIS 3.4 for Instance 2
- Database: `fitpub2`
- Port: 5432 (internal), 5433 (on host)
- **fitpub1**: FitPub Instance 1
- Domain: `instance1.local:8080`
- Port: 8080
- Network alias: `instance1.local`
- **fitpub2**: FitPub Instance 2
- Domain: `instance2.local:8081`
- Port: 8081
- Network alias: `instance2.local`
### Docker-Specific Commands
**Access database directly**:
```bash
# Instance 1 database
docker exec -it fitpub-postgres1 psql -U fitpub -d fitpub1
# Instance 2 database
docker exec -it fitpub-postgres2 psql -U fitpub -d fitpub2
```
**Inspect network**:
```bash
docker network inspect fitpub-federation
```
**View container details**:
```bash
docker inspect fitpub-instance1
docker inspect fitpub-instance2
```
**Restart a single service**:
```bash
docker-compose -f docker-compose.federation-test.yml restart fitpub1
docker-compose -f docker-compose.federation-test.yml restart fitpub2
```
**Rebuild images** (after code changes):
```bash
docker-compose -f docker-compose.federation-test.yml build
docker-compose -f docker-compose.federation-test.yml up -d
```
### Docker Troubleshooting
**Container won't start**:
```bash
# Check logs for errors
docker-compose -f docker-compose.federation-test.yml logs fitpub1
docker-compose -f docker-compose.federation-test.yml logs fitpub2
# Check health status
docker ps -a | grep fitpub
```
**Database connection issues**:
```bash
# Verify database is healthy
docker-compose -f docker-compose.federation-test.yml ps postgres1
docker-compose -f docker-compose.federation-test.yml ps postgres2
# Check database logs
docker-compose -f docker-compose.federation-test.yml logs postgres1
```
**Network connectivity issues**:
```bash
# Test DNS resolution from inside container
docker exec -it fitpub-instance1 ping instance2.local
docker exec -it fitpub-instance2 ping instance1.local
# Test HTTP connectivity
docker exec -it fitpub-instance1 curl http://instance2.local:8081/.well-known/webfinger
```
**Port already in use**:
```bash
# Find process using port 8080
lsof -ti:8080 | xargs kill -9
# Or use different external ports in docker-compose.yml
```
**Volume permission issues**:
```bash
# Remove all volumes and start fresh
docker-compose -f docker-compose.federation-test.yml down -v
docker-compose -f docker-compose.federation-test.yml up -d
```
**Platform warning on Apple Silicon (M1/M2/M3 Macs)**:
```
The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8)
```
This is expected and safe to ignore. Docker will use emulation (Rosetta 2) to run the amd64 images. Performance may be slightly slower than native ARM images, but fully functional for testing.
---
## Manual Setup (Alternative)
If you prefer to run the instances directly without Docker, follow these instructions:
### 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